DialtactsActivity.java revision e142481570d7fbda5d035555fe217314e396ae90
1/*
2 * Copyright (C) 2013 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;
18
19import android.animation.Animator;
20import android.animation.Animator.AnimatorListener;
21import android.animation.AnimatorListenerAdapter;
22import android.app.Activity;
23import android.app.backup.BackupManager;
24import android.app.Fragment;
25import android.app.FragmentManager;
26import android.app.FragmentManager.BackStackEntry;
27import android.app.FragmentTransaction;
28import android.content.ActivityNotFoundException;
29import android.content.Context;
30import android.content.Intent;
31import android.content.SharedPreferences;
32import android.content.res.Resources;
33import android.net.Uri;
34import android.os.Bundle;
35import android.os.RemoteException;
36import android.os.ServiceManager;
37import android.provider.CallLog.Calls;
38import android.provider.ContactsContract;
39import android.provider.ContactsContract.Contacts;
40import android.provider.ContactsContract.Intents;
41import android.provider.ContactsContract.Intents.UI;
42import android.provider.Settings;
43import android.speech.RecognizerIntent;
44import android.support.v4.app.NavUtils;
45import android.telephony.TelephonyManager;
46import android.text.Editable;
47import android.text.TextUtils;
48import android.text.TextWatcher;
49import android.util.Log;
50import android.view.Menu;
51import android.view.MenuItem;
52import android.view.View;
53import android.view.View.OnFocusChangeListener;
54import android.view.ViewConfiguration;
55import android.view.inputmethod.InputMethodManager;
56import android.widget.AbsListView.OnScrollListener;
57import android.widget.EditText;
58import android.widget.ImageView;
59import android.widget.PopupMenu;
60import android.widget.SearchView;
61import android.widget.SearchView.OnCloseListener;
62import android.widget.SearchView.OnQueryTextListener;
63import android.widget.Toast;
64
65import com.android.contacts.common.CallUtil;
66import com.android.contacts.common.activity.TransactionSafeActivity;
67import com.android.contacts.common.dialog.ClearFrequentsDialog;
68import com.android.contacts.common.interactions.ImportExportDialogFragment;
69import com.android.contacts.common.list.ContactListItemView;
70import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
71import com.android.contacts.common.list.PhoneNumberPickerFragment;
72import com.android.dialer.calllog.CallLogActivity;
73import com.android.dialer.database.DialerDatabaseHelper;
74import com.android.dialer.dialpad.DialpadFragment;
75import com.android.dialer.dialpad.SmartDialNameMatcher;
76import com.android.dialer.dialpad.SmartDialPrefix;
77import com.android.dialer.interactions.PhoneNumberInteraction;
78import com.android.dialer.list.AllContactsActivity;
79import com.android.dialer.list.PhoneFavoriteFragment;
80import com.android.dialer.list.OnListFragmentScrolledListener;
81import com.android.dialer.list.RegularSearchFragment;
82import com.android.dialer.list.SearchFragment;
83import com.android.dialer.list.SmartDialSearchFragment;
84import com.android.dialerbind.DatabaseHelperManager;
85import com.android.internal.telephony.ITelephony;
86
87import java.util.ArrayList;
88
89/**
90 * The dialer tab's title is 'phone', a more common name (see strings.xml).
91 */
92public class DialtactsActivity extends TransactionSafeActivity implements View.OnClickListener,
93        DialpadFragment.OnDialpadQueryChangedListener, PopupMenu.OnMenuItemClickListener,
94        OnListFragmentScrolledListener,
95        DialpadFragment.OnDialpadFragmentStartedListener,
96        PhoneFavoriteFragment.OnShowAllContactsListener {
97    private static final String TAG = "DialtactsActivity";
98
99    public static final boolean DEBUG = false;
100
101    public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences";
102
103    /** Used to open Call Setting */
104    private static final String PHONE_PACKAGE = "com.android.phone";
105    private static final String CALL_SETTINGS_CLASS_NAME =
106            "com.android.phone.CallFeaturesSetting";
107    /** @see #getCallOrigin() */
108    private static final String CALL_ORIGIN_DIALTACTS =
109            "com.android.dialer.DialtactsActivity";
110
111    private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui";
112    private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui";
113    private static final String KEY_SEARCH_QUERY = "search_query";
114    private static final String KEY_FIRST_LAUNCH = "first_launch";
115
116    private static final String TAG_DIALPAD_FRAGMENT = "dialpad";
117    private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search";
118    private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial";
119    private static final String TAG_FAVORITES_FRAGMENT = "favorites";
120
121    /**
122     * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}.
123     */
124    private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER";
125
126    private static final int SUBACTIVITY_ACCOUNT_FILTER = 1;
127
128    private static final int ACTIVITY_REQUEST_CODE_VOICE_SEARCH = 1;
129
130    private String mFilterText;
131
132    /**
133     * The main fragment displaying the user's favorites and frequent contacts
134     */
135    private PhoneFavoriteFragment mPhoneFavoriteFragment;
136
137    /**
138     * Fragment containing the dialpad that slides into view
139     */
140    private DialpadFragment mDialpadFragment;
141
142    /**
143     * Fragment for searching phone numbers using the alphanumeric keyboard.
144     */
145    private RegularSearchFragment mRegularSearchFragment;
146
147    /**
148     * Fragment for searching phone numbers using the dialpad.
149     */
150    private SmartDialSearchFragment mSmartDialSearchFragment;
151
152    private View mMenuButton;
153    private View mCallHistoryButton;
154    private View mDialpadButton;
155
156    // Padding view used to shift the fragments up when the dialpad is shown.
157    private View mBottomPaddingView;
158    private View mFragmentsFrame;
159
160    private boolean mInDialpadSearch;
161    private boolean mInRegularSearch;
162
163    /**
164     * True when this activity has been launched for the first time.
165     */
166    private boolean mFirstLaunch;
167    private View mSearchViewContainer;
168    private View mSearchViewCloseButton;
169    private View mVoiceSearchButton;
170    private EditText mSearchView;
171
172    private String mSearchQuery;
173
174    private DialerDatabaseHelper mDialerDatabaseHelper;
175
176    /**
177     * Listener used when one of phone numbers in search UI is selected. This will initiate a
178     * phone call using the phone number.
179     */
180    private final OnPhoneNumberPickerActionListener mPhoneNumberPickerActionListener =
181            new OnPhoneNumberPickerActionListener() {
182                @Override
183                public void onPickPhoneNumberAction(Uri dataUri) {
184                    // Specify call-origin so that users will see the previous tab instead of
185                    // CallLog screen (search UI will be automatically exited).
186                    PhoneNumberInteraction.startInteractionForPhoneCall(
187                        DialtactsActivity.this, dataUri, getCallOrigin());
188                    hideDialpadAndSearchUi();
189                }
190
191                @Override
192                public void onCallNumberDirectly(String phoneNumber) {
193                    Intent intent = CallUtil.getCallIntent(phoneNumber, getCallOrigin());
194                    startActivity(intent);
195                    hideDialpadAndSearchUi();
196                }
197
198                @Override
199                public void onShortcutIntentCreated(Intent intent) {
200                    Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring.");
201                }
202
203                @Override
204                public void onHomeInActionBarSelected() {
205                    exitSearchUi();
206                }
207    };
208
209    /**
210     * Listener used to send search queries to the phone search fragment.
211     */
212    private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() {
213            @Override
214            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
215            }
216
217            @Override
218            public void onTextChanged(CharSequence s, int start, int before, int count) {
219                final String newText = s.toString();
220                if (newText.equals(mSearchQuery)) {
221                    // If the query hasn't changed (perhaps due to activity being destroyed
222                    // and restored, or user launching the same DIAL intent twice), then there is
223                    // no need to do anything here.
224                    return;
225                }
226                mSearchQuery = newText;
227                if (DEBUG) {
228                    Log.d(TAG, "onTextChange for mSearchView called with new query: " + s);
229                }
230                final boolean dialpadSearch = isDialpadShowing();
231
232                // Show search result with non-empty text. Show a bare list otherwise.
233                if (TextUtils.isEmpty(newText) && getInSearchUi()) {
234                    exitSearchUi();
235                    mSearchViewCloseButton.setVisibility(View.GONE);
236                    return;
237                } else if (!TextUtils.isEmpty(newText)) {
238                    final boolean sameSearchMode = (dialpadSearch && mInDialpadSearch) ||
239                            (!dialpadSearch && mInRegularSearch);
240                    if (!sameSearchMode) {
241                        // call enterSearchUi only if we are switching search modes, or entering
242                        // search ui for the first time
243                        enterSearchUi(dialpadSearch, newText);
244                    }
245
246                    if (dialpadSearch && mSmartDialSearchFragment != null) {
247                            mSmartDialSearchFragment.setQueryString(newText, false);
248                    } else if (mRegularSearchFragment != null) {
249                        mRegularSearchFragment.setQueryString(newText, false);
250                    }
251                    mSearchViewCloseButton.setVisibility(View.VISIBLE);
252                    return;
253                }
254            }
255
256            @Override
257            public void afterTextChanged(Editable s) {
258            }
259    };
260
261    private boolean isDialpadShowing() {
262        return mDialpadFragment != null && mDialpadFragment.isVisible();
263    }
264
265    @Override
266    protected void onCreate(Bundle savedInstanceState) {
267        super.onCreate(savedInstanceState);
268        mFirstLaunch = true;
269
270        final Intent intent = getIntent();
271        fixIntent(intent);
272
273        setContentView(R.layout.dialtacts_activity);
274
275        getActionBar().hide();
276
277        // Add the favorites fragment, and the dialpad fragment, but only if savedInstanceState
278        // is null. Otherwise the fragment manager takes care of recreating these fragments.
279        if (savedInstanceState == null) {
280            final PhoneFavoriteFragment phoneFavoriteFragment = new PhoneFavoriteFragment();
281
282            final FragmentTransaction ft = getFragmentManager().beginTransaction();
283            ft.add(R.id.dialtacts_frame, phoneFavoriteFragment, TAG_FAVORITES_FRAGMENT);
284            ft.add(R.id.dialtacts_container, new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
285            ft.commit();
286        } else {
287            mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY);
288            mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI);
289            mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI);
290            mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH);
291        }
292
293        mBottomPaddingView = findViewById(R.id.dialtacts_bottom_padding);
294        mFragmentsFrame = findViewById(R.id.dialtacts_frame);
295        prepareSearchView();
296
297        if (UI.FILTER_CONTACTS_ACTION.equals(intent.getAction())
298                && savedInstanceState == null) {
299            setupFilterText(intent);
300        }
301
302        setupFakeActionBarItems();
303
304        mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this);
305        SmartDialPrefix.initializeNanpSettings(this);
306    }
307
308    @Override
309    protected void onResume() {
310        super.onResume();
311        if (mFirstLaunch) {
312            displayFragment(getIntent());
313        }
314        mFirstLaunch = false;
315        mDialerDatabaseHelper.startSmartDialUpdateThread();
316    }
317
318    @Override
319    protected void onSaveInstanceState(Bundle outState) {
320        super.onSaveInstanceState(outState);
321        outState.putString(KEY_SEARCH_QUERY, mSearchQuery);
322        outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch);
323        outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch);
324        outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch);
325    }
326
327    @Override
328    public void onAttachFragment(Fragment fragment) {
329        if (fragment instanceof DialpadFragment) {
330            mDialpadFragment = (DialpadFragment) fragment;
331            final FragmentTransaction transaction = getFragmentManager().beginTransaction();
332            transaction.hide(mDialpadFragment);
333            transaction.commit();
334        } else if (fragment instanceof SmartDialSearchFragment) {
335            mSmartDialSearchFragment = (SmartDialSearchFragment) fragment;
336            mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(
337                    mPhoneNumberPickerActionListener);
338        } else if (fragment instanceof SearchFragment) {
339            mRegularSearchFragment = (RegularSearchFragment) fragment;
340            mRegularSearchFragment.setOnPhoneNumberPickerActionListener(
341                    mPhoneNumberPickerActionListener);
342        } else if (fragment instanceof PhoneFavoriteFragment) {
343            mPhoneFavoriteFragment = (PhoneFavoriteFragment) fragment;
344            mPhoneFavoriteFragment.setListener(mPhoneFavoriteListener);
345        }
346    }
347
348    @Override
349    public boolean onMenuItemClick(MenuItem item) {
350        switch (item.getItemId()) {
351            case R.id.menu_import_export:
352                // We hard-code the "contactsAreAvailable" argument because doing it properly would
353                // involve querying a {@link ProviderStatusLoader}, which we don't want to do right
354                // now in Dialtacts for (potential) performance reasons. Compare with how it is
355                // done in {@link PeopleActivity}.
356                ImportExportDialogFragment.show(getFragmentManager(), true,
357                        DialtactsActivity.class);
358                return true;
359            case R.id.menu_clear_frequents:
360                ClearFrequentsDialog.show(getFragmentManager());
361                return true;
362            case R.id.menu_add_contact:
363                try {
364                    startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
365                } catch (ActivityNotFoundException e) {
366                    Toast toast = Toast.makeText(this,
367                            R.string.add_contact_not_available,
368                            Toast.LENGTH_SHORT);
369                    toast.show();
370                }
371                return true;
372            case R.id.menu_call_settings:
373                final Intent settingsIntent = DialtactsActivity.getCallSettingsIntent();
374                startActivity(settingsIntent);
375                return true;
376            case R.id.menu_all_contacts:
377                onShowAllContacts();
378                return true;
379        }
380        return false;
381    }
382
383    @Override
384    public void onClick(View view) {
385        switch (view.getId()) {
386            case R.id.overflow_menu: {
387                final PopupMenu popupMenu = new PopupMenu(DialtactsActivity.this, view);
388                final Menu menu = popupMenu.getMenu();
389                popupMenu.inflate(R.menu.dialtacts_options);
390                final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents);
391                clearFrequents.setVisible(mPhoneFavoriteFragment.hasFrequents());
392                popupMenu.setOnMenuItemClickListener(this);
393                popupMenu.show();
394                break;
395            }
396            case R.id.dialpad_button:
397                showDialpadFragment(true);
398                break;
399            case R.id.call_history_on_dialpad_button:
400            case R.id.call_history_button:
401                // Use explicit CallLogActivity intent instead of ACTION_VIEW +
402                // CONTENT_TYPE, so that we always open our call log from our dialer
403                final Intent intent = new Intent(this, CallLogActivity.class);
404                startActivity(intent);
405                break;
406            case R.id.search_close_button:
407                // Clear the search field
408                if (!TextUtils.isEmpty(mSearchView.getText())) {
409                    mSearchView.setText("");
410                }
411                break;
412            case R.id.voice_search_button:
413                final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
414                startActivityForResult(voiceIntent, ACTIVITY_REQUEST_CODE_VOICE_SEARCH);
415                break;
416            default: {
417                Log.wtf(TAG, "Unexpected onClick event from " + view);
418                break;
419            }
420        }
421    }
422
423    @Override
424    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
425        if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) {
426            if (resultCode == RESULT_OK) {
427                final ArrayList<String> matches = data.getStringArrayListExtra(
428                        RecognizerIntent.EXTRA_RESULTS);
429                if (matches.size() > 0) {
430                    final String match = matches.get(0);
431                    mSearchView.setText(match);
432                } else {
433                    Log.e(TAG, "Voice search - nothing heard");
434                }
435            } else {
436                Log.e(TAG, "Voice search failed");
437            }
438        }
439        super.onActivityResult(requestCode, resultCode, data);
440    }
441
442    private void showDialpadFragment(boolean animate) {
443        mDialpadFragment.setAdjustTranslationForAnimation(animate);
444        final FragmentTransaction ft = getFragmentManager().beginTransaction();
445        if (animate) {
446            ft.setCustomAnimations(R.anim.slide_in, 0);
447        } else {
448            mDialpadFragment.setYFraction(0);
449        }
450        ft.show(mDialpadFragment);
451        ft.commit();
452    }
453
454    private void hideDialpadFragment(boolean animate, boolean clearDialpad) {
455        if (mDialpadFragment == null) return;
456        if (clearDialpad) {
457            mDialpadFragment.clearDialpad();
458        }
459        if (!mDialpadFragment.isVisible()) return;
460        mDialpadFragment.setAdjustTranslationForAnimation(animate);
461        final FragmentTransaction ft = getFragmentManager().beginTransaction();
462        if (animate) {
463            ft.setCustomAnimations(0, R.anim.slide_out);
464        }
465        ft.hide(mDialpadFragment);
466        ft.commit();
467    }
468
469    private void prepareSearchView() {
470        mSearchViewContainer = findViewById(R.id.search_view_container);
471        mSearchViewCloseButton = findViewById(R.id.search_close_button);
472        mSearchViewCloseButton.setOnClickListener(this);
473        mVoiceSearchButton = findViewById(R.id.voice_search_button);
474        mVoiceSearchButton.setOnClickListener(this);
475        mSearchView = (EditText) findViewById(R.id.search_view);
476        mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
477        mSearchView.setHint(getString(R.string.dialer_hint_find_contact));
478        mSearchView.setOnFocusChangeListener(new OnFocusChangeListener() {
479            @Override
480            public void onFocusChange(View view, boolean hasFocus) {
481                if (hasFocus) {
482                    showInputMethod(view.findFocus());
483                }
484            }
485        });
486    }
487
488    final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
489        @Override
490        public void onAnimationEnd(Animator animation) {
491            mSearchViewContainer.setVisibility(View.GONE);
492        }
493    };
494
495    private boolean getInSearchUi() {
496        return mInDialpadSearch || mInRegularSearch;
497    }
498
499    private void setNotInSearchUi() {
500        mInDialpadSearch = false;
501        mInRegularSearch = false;
502    }
503
504    private void hideDialpadAndSearchUi() {
505        mSearchView.setText(null);
506        hideDialpadFragment(false, true);
507    }
508
509    public void hideSearchBar() {
510       hideSearchBar(true);
511    }
512
513    public void hideSearchBar(boolean shiftView) {
514        if (shiftView) {
515            mSearchViewContainer.animate().cancel();
516            mSearchViewContainer.setAlpha(1);
517            mSearchViewContainer.setTranslationY(0);
518            mSearchViewContainer.animate().withLayer().alpha(0).translationY(-mSearchView.getHeight())
519                    .setDuration(200).setListener(mHideListener);
520
521            mFragmentsFrame.animate().withLayer()
522                    .translationY(-mSearchViewContainer.getHeight()).setDuration(200).setListener(
523                    new AnimatorListenerAdapter() {
524                        @Override
525                        public void onAnimationEnd(Animator animation) {
526                            mBottomPaddingView.setVisibility(View.VISIBLE);
527                                mFragmentsFrame.setTranslationY(0);
528                        }
529                    });
530        } else {
531            mSearchViewContainer.setTranslationY(-mSearchView.getHeight());
532        }
533    }
534
535    public void showSearchBar() {
536        mSearchViewContainer.animate().cancel();
537        mSearchViewContainer.setAlpha(0);
538        mSearchViewContainer.setTranslationY(-mSearchViewContainer.getHeight());
539        mSearchViewContainer.animate().withLayer().alpha(1).translationY(0).setDuration(200)
540                .setListener(new AnimatorListenerAdapter() {
541                    @Override
542                    public void onAnimationStart(Animator animation) {
543                        mSearchViewContainer.setVisibility(View.VISIBLE);
544                    }
545                });
546
547        mFragmentsFrame.setTranslationY(-mSearchViewContainer.getHeight());
548        mFragmentsFrame.animate().withLayer().translationY(0).setDuration(200)
549                .setListener(
550                        new AnimatorListenerAdapter() {
551                            @Override
552                            public void onAnimationStart(Animator animation) {
553                                mBottomPaddingView.setVisibility(View.GONE);
554                            }
555                        });
556    }
557
558
559    public void setupFakeActionBarItems() {
560        mMenuButton = findViewById(R.id.overflow_menu);
561        if (mMenuButton != null) {
562            mMenuButton.setOnClickListener(this);
563        }
564
565        mCallHistoryButton = findViewById(R.id.call_history_button);
566        // mCallHistoryButton.setMinimumWidth(fakeMenuItemWidth);
567        mCallHistoryButton.setOnClickListener(this);
568
569        mDialpadButton = findViewById(R.id.dialpad_button);
570        // DialpadButton.setMinimumWidth(fakeMenuItemWidth);
571        mDialpadButton.setOnClickListener(this);
572    }
573
574    public void setupFakeActionBarItemsForDialpadFragment() {
575        final View callhistoryButton = findViewById(R.id.call_history_on_dialpad_button);
576        callhistoryButton.setOnClickListener(this);
577    }
578
579    private void fixIntent(Intent intent) {
580        // This should be cleaned up: the call key used to send an Intent
581        // that just said to go to the recent calls list.  It now sends this
582        // abstract action, but this class hasn't been rewritten to deal with it.
583        if (Intent.ACTION_CALL_BUTTON.equals(intent.getAction())) {
584            intent.setDataAndType(Calls.CONTENT_URI, Calls.CONTENT_TYPE);
585            intent.putExtra("call_key", true);
586            setIntent(intent);
587        }
588    }
589
590    /**
591     * Returns true if the intent is due to hitting the green send key (hardware call button:
592     * KEYCODE_CALL) while in a call.
593     *
594     * @param intent the intent that launched this activity
595     * @param recentCallsRequest true if the intent is requesting to view recent calls
596     * @return true if the intent is due to hitting the green send key while in a call
597     */
598    private boolean isSendKeyWhileInCall(Intent intent, boolean recentCallsRequest) {
599        // If there is a call in progress go to the call screen
600        if (recentCallsRequest) {
601            final boolean callKey = intent.getBooleanExtra("call_key", false);
602
603            try {
604                ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
605                if (callKey && phone != null && phone.showCallScreen()) {
606                    return true;
607                }
608            } catch (RemoteException e) {
609                Log.e(TAG, "Failed to handle send while in call", e);
610            }
611        }
612
613        return false;
614    }
615
616    /**
617     * Sets the current tab based on the intent's request type
618     *
619     * @param intent Intent that contains information about which tab should be selected
620     */
621    private void displayFragment(Intent intent) {
622        // If we got here by hitting send and we're in call forward along to the in-call activity
623        boolean recentCallsRequest = Calls.CONTENT_TYPE.equals(intent.resolveType(
624            getContentResolver()));
625        if (isSendKeyWhileInCall(intent, recentCallsRequest)) {
626            finish();
627            return;
628        }
629
630        if (mDialpadFragment != null && (phoneIsInUse() || isDialIntent(intent))) {
631            mDialpadFragment.setStartedFromNewIntent(true);
632            showDialpadFragment(false);
633        }
634    }
635
636    @Override
637    public void onNewIntent(Intent newIntent) {
638        setIntent(newIntent);
639        fixIntent(newIntent);
640        displayFragment(newIntent);
641        final String action = newIntent.getAction();
642
643        invalidateOptionsMenu();
644    }
645
646    /** Returns true if the given intent contains a phone number to populate the dialer with */
647    private boolean isDialIntent(Intent intent) {
648        final String action = intent.getAction();
649        if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {
650            return true;
651        }
652        if (Intent.ACTION_VIEW.equals(action)) {
653            final Uri data = intent.getData();
654            if (data != null && CallUtil.SCHEME_TEL.equals(data.getScheme())) {
655                return true;
656            }
657        }
658        return false;
659    }
660
661    /**
662     * Returns an appropriate call origin for this Activity. May return null when no call origin
663     * should be used (e.g. when some 3rd party application launched the screen. Call origin is
664     * for remembering the tab in which the user made a phone call, so the external app's DIAL
665     * request should not be counted.)
666     */
667    public String getCallOrigin() {
668        return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null;
669    }
670
671    /**
672     * Retrieves the filter text stored in {@link #setupFilterText(Intent)}.
673     * This text originally came from a FILTER_CONTACTS_ACTION intent received
674     * by this activity. The stored text will then be cleared after after this
675     * method returns.
676     *
677     * @return The stored filter text
678     */
679    public String getAndClearFilterText() {
680        String filterText = mFilterText;
681        mFilterText = null;
682        return filterText;
683    }
684
685    /**
686     * Stores the filter text associated with a FILTER_CONTACTS_ACTION intent.
687     * This is so child activities can check if they are supposed to display a filter.
688     *
689     * @param intent The intent received in {@link #onNewIntent(Intent)}
690     */
691    private void setupFilterText(Intent intent) {
692        // If the intent was relaunched from history, don't apply the filter text.
693        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
694            return;
695        }
696        String filter = intent.getStringExtra(UI.FILTER_TEXT_EXTRA_KEY);
697        if (filter != null && filter.length() > 0) {
698            mFilterText = filter;
699        }
700    }
701
702    private final PhoneFavoriteFragment.Listener mPhoneFavoriteListener =
703            new PhoneFavoriteFragment.Listener() {
704        @Override
705        public void onContactSelected(Uri contactUri) {
706            PhoneNumberInteraction.startInteractionForPhoneCall(
707                        DialtactsActivity.this, contactUri, getCallOrigin());
708        }
709
710        @Override
711        public void onCallNumberDirectly(String phoneNumber) {
712            Intent intent = CallUtil.getCallIntent(phoneNumber, getCallOrigin());
713            startActivity(intent);
714        }
715    };
716
717    /* TODO krelease: This is only relevant for phones that have a hard button search key (i.e.
718     * Nexus S). Supporting it is a little more tricky because of the dialpad fragment might
719     * be showing when the search key is pressed so there is more state management involved.
720
721    @Override
722    public void startSearch(String initialQuery, boolean selectInitialQuery,
723            Bundle appSearchData, boolean globalSearch) {
724        if (mRegularSearchFragment != null && mRegularSearchFragment.isAdded() && !globalSearch) {
725            if (mInSearchUi) {
726                if (mSearchView.hasFocus()) {
727                    showInputMethod(mSearchView.findFocus());
728                } else {
729                    mSearchView.requestFocus();
730                }
731            } else {
732                enterSearchUi();
733            }
734        } else {
735            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
736        }
737    }*/
738
739    private void showInputMethod(View view) {
740        final InputMethodManager imm = (InputMethodManager) getSystemService(
741                Context.INPUT_METHOD_SERVICE);
742        if (imm != null) {
743            imm.showSoftInput(view, 0);
744        }
745    }
746
747    private void hideInputMethod(View view) {
748        final InputMethodManager imm = (InputMethodManager) getSystemService(
749                Context.INPUT_METHOD_SERVICE);
750        if (imm != null && view != null) {
751            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
752        }
753    }
754
755    /**
756     * Shows the search fragment
757     */
758    private void enterSearchUi(boolean smartDialSearch, String query) {
759        if (getFragmentManager().isDestroyed()) {
760            // Weird race condition where fragment is doing work after the activity is destroyed
761            // due to talkback being on (b/10209937). Just return since we can't do any
762            // constructive here.
763            return;
764        }
765
766        if (DEBUG) {
767            Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch);
768        }
769
770        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
771        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
772
773        SearchFragment fragment;
774        if (mInDialpadSearch) {
775            transaction.remove(mSmartDialSearchFragment);
776        } else if (mInRegularSearch) {
777            transaction.remove(mRegularSearchFragment);
778        } else {
779            transaction.remove(mPhoneFavoriteFragment);
780        }
781
782        final String tag;
783        if (smartDialSearch) {
784            tag = TAG_SMARTDIAL_SEARCH_FRAGMENT;
785        } else {
786            tag = TAG_REGULAR_SEARCH_FRAGMENT;
787        }
788        mInDialpadSearch = smartDialSearch;
789        mInRegularSearch = !smartDialSearch;
790
791        fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag);
792        if (fragment == null) {
793            if (smartDialSearch) {
794                fragment = new SmartDialSearchFragment();
795            } else {
796                fragment = new RegularSearchFragment();
797            }
798        }
799        transaction.replace(R.id.dialtacts_frame, fragment, tag);
800        transaction.addToBackStack(null);
801        fragment.setQueryString(query, false);
802        transaction.commit();
803    }
804
805    /**
806     * Hides the search fragment
807     */
808    private void exitSearchUi() {
809        // Go all the way back to the favorites fragment, regardless of how many times we
810        // transitioned between search fragments
811        final BackStackEntry entry = getFragmentManager().getBackStackEntryAt(0);
812        getFragmentManager().popBackStack(0, FragmentManager.POP_BACK_STACK_INCLUSIVE);
813        setNotInSearchUi();
814    }
815
816    /** Returns an Intent to launch Call Settings screen */
817    public static Intent getCallSettingsIntent() {
818        final Intent intent = new Intent(Intent.ACTION_MAIN);
819        intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME);
820        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
821        return intent;
822    }
823
824    @Override
825    public void onBackPressed() {
826        if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
827            hideDialpadFragment(true, false);
828        } else if (getInSearchUi()) {
829            mSearchView.setText(null);
830            mDialpadFragment.clearDialpad();
831        } else if (isTaskRoot()) {
832            // Instead of stopping, simply push this to the back of the stack.
833            // This is only done when running at the top of the stack;
834            // otherwise, we have been launched by someone else so need to
835            // allow the user to go back to the caller.
836            moveTaskToBack(false);
837        } else {
838            super.onBackPressed();
839        }
840    }
841
842    @Override
843    public void onDialpadQueryChanged(String query) {
844        final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,
845                SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
846        if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
847            if (DEBUG) {
848                Log.d(TAG, "onDialpadQueryChanged - new query: " + query);
849            }
850            if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
851                // This callback can happen if the dialpad fragment is recreated because of
852                // activity destruction. In that case, don't update the search view because
853                // that would bring the user back to the search fragment regardless of the
854                // previous state of the application. Instead, just return here and let the
855                // fragment manager correctly figure out whatever fragment was last displayed.
856                return;
857            }
858            mSearchView.setText(normalizedQuery);
859        }
860    }
861
862    @Override
863    public void onListFragmentScrollStateChange(int scrollState) {
864        if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
865            hideDialpadFragment(true, false);
866            hideInputMethod(getCurrentFocus());
867        }
868    }
869
870    @Override
871    public void onDialpadFragmentStarted() {
872        setupFakeActionBarItemsForDialpadFragment();
873    }
874
875    private boolean phoneIsInUse() {
876        final TelephonyManager tm = (TelephonyManager) getSystemService(
877                Context.TELEPHONY_SERVICE);
878        return tm.getCallState() != TelephonyManager.CALL_STATE_IDLE;
879    }
880
881    @Override
882    public void onShowAllContacts() {
883        final Intent intent = new Intent(this, AllContactsActivity.class);
884        startActivity(intent);
885    }
886
887    public static Intent getAddNumberToContactIntent(CharSequence text) {
888        final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
889        intent.putExtra(Intents.Insert.PHONE, text);
890        intent.setType(Contacts.CONTENT_ITEM_TYPE);
891        return intent;
892    }
893
894    public static Intent getInsertContactWithNameIntent(CharSequence text) {
895        final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
896        intent.putExtra(Intents.Insert.NAME, text);
897        return intent;
898    }
899}
900