DialtactsActivity.java revision 2eafb7afc46135bbbf5a79b437df33cd0fb66c0b
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                    mVoiceSearchButton.setVisibility(View.VISIBLE);
237                    return;
238                } else if (!TextUtils.isEmpty(newText)) {
239                    final boolean sameSearchMode = (dialpadSearch && mInDialpadSearch) ||
240                            (!dialpadSearch && mInRegularSearch);
241                    if (!sameSearchMode) {
242                        // call enterSearchUi only if we are switching search modes, or entering
243                        // search ui for the first time
244                        enterSearchUi(dialpadSearch, newText);
245                    }
246
247                    if (dialpadSearch && mSmartDialSearchFragment != null) {
248                            mSmartDialSearchFragment.setQueryString(newText, false);
249                    } else if (mRegularSearchFragment != null) {
250                        mRegularSearchFragment.setQueryString(newText, false);
251                    }
252                    mSearchViewCloseButton.setVisibility(View.VISIBLE);
253                    mVoiceSearchButton.setVisibility(View.GONE);
254                    return;
255                }
256            }
257
258            @Override
259            public void afterTextChanged(Editable s) {
260            }
261    };
262
263    private boolean isDialpadShowing() {
264        return mDialpadFragment != null && mDialpadFragment.isVisible();
265    }
266
267    @Override
268    protected void onCreate(Bundle savedInstanceState) {
269        super.onCreate(savedInstanceState);
270        mFirstLaunch = true;
271
272        final Intent intent = getIntent();
273        fixIntent(intent);
274
275        setContentView(R.layout.dialtacts_activity);
276
277        getActionBar().hide();
278
279        // Add the favorites fragment, and the dialpad fragment, but only if savedInstanceState
280        // is null. Otherwise the fragment manager takes care of recreating these fragments.
281        if (savedInstanceState == null) {
282            final PhoneFavoriteFragment phoneFavoriteFragment = new PhoneFavoriteFragment();
283
284            final FragmentTransaction ft = getFragmentManager().beginTransaction();
285            ft.add(R.id.dialtacts_frame, phoneFavoriteFragment, TAG_FAVORITES_FRAGMENT);
286            ft.add(R.id.dialtacts_container, new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
287            ft.commit();
288        } else {
289            mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY);
290            mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI);
291            mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI);
292            mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH);
293        }
294
295        mBottomPaddingView = findViewById(R.id.dialtacts_bottom_padding);
296        mFragmentsFrame = findViewById(R.id.dialtacts_frame);
297        prepareSearchView();
298
299        if (UI.FILTER_CONTACTS_ACTION.equals(intent.getAction())
300                && savedInstanceState == null) {
301            setupFilterText(intent);
302        }
303
304        setupFakeActionBarItems();
305
306        mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this);
307        SmartDialPrefix.initializeNanpSettings(this);
308    }
309
310    @Override
311    protected void onResume() {
312        super.onResume();
313        if (mFirstLaunch) {
314            displayFragment(getIntent());
315        }
316        mFirstLaunch = false;
317        mDialerDatabaseHelper.startSmartDialUpdateThread();
318    }
319
320    @Override
321    protected void onSaveInstanceState(Bundle outState) {
322        super.onSaveInstanceState(outState);
323        outState.putString(KEY_SEARCH_QUERY, mSearchQuery);
324        outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch);
325        outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch);
326        outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch);
327    }
328
329    @Override
330    public void onAttachFragment(Fragment fragment) {
331        if (fragment instanceof DialpadFragment) {
332            mDialpadFragment = (DialpadFragment) fragment;
333            final FragmentTransaction transaction = getFragmentManager().beginTransaction();
334            transaction.hide(mDialpadFragment);
335            transaction.commit();
336        } else if (fragment instanceof SmartDialSearchFragment) {
337            mSmartDialSearchFragment = (SmartDialSearchFragment) fragment;
338            mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(
339                    mPhoneNumberPickerActionListener);
340        } else if (fragment instanceof SearchFragment) {
341            mRegularSearchFragment = (RegularSearchFragment) fragment;
342            mRegularSearchFragment.setOnPhoneNumberPickerActionListener(
343                    mPhoneNumberPickerActionListener);
344        } else if (fragment instanceof PhoneFavoriteFragment) {
345            mPhoneFavoriteFragment = (PhoneFavoriteFragment) fragment;
346            mPhoneFavoriteFragment.setListener(mPhoneFavoriteListener);
347        }
348    }
349
350    @Override
351    public boolean onMenuItemClick(MenuItem item) {
352        switch (item.getItemId()) {
353            case R.id.menu_import_export:
354                // We hard-code the "contactsAreAvailable" argument because doing it properly would
355                // involve querying a {@link ProviderStatusLoader}, which we don't want to do right
356                // now in Dialtacts for (potential) performance reasons. Compare with how it is
357                // done in {@link PeopleActivity}.
358                ImportExportDialogFragment.show(getFragmentManager(), true,
359                        DialtactsActivity.class);
360                return true;
361            case R.id.menu_clear_frequents:
362                ClearFrequentsDialog.show(getFragmentManager());
363                return true;
364            case R.id.menu_add_contact:
365                try {
366                    startActivity(new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
367                } catch (ActivityNotFoundException e) {
368                    Toast toast = Toast.makeText(this,
369                            R.string.add_contact_not_available,
370                            Toast.LENGTH_SHORT);
371                    toast.show();
372                }
373                return true;
374            case R.id.menu_call_settings:
375                final Intent settingsIntent = DialtactsActivity.getCallSettingsIntent();
376                startActivity(settingsIntent);
377                return true;
378            case R.id.menu_all_contacts:
379                onShowAllContacts();
380                return true;
381        }
382        return false;
383    }
384
385    @Override
386    public void onClick(View view) {
387        switch (view.getId()) {
388            case R.id.overflow_menu: {
389                final PopupMenu popupMenu = new PopupMenu(DialtactsActivity.this, view);
390                final Menu menu = popupMenu.getMenu();
391                popupMenu.inflate(R.menu.dialtacts_options);
392                final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents);
393                clearFrequents.setVisible(mPhoneFavoriteFragment.hasFrequents());
394                popupMenu.setOnMenuItemClickListener(this);
395                popupMenu.show();
396                break;
397            }
398            case R.id.dialpad_button:
399                showDialpadFragment(true);
400                break;
401            case R.id.call_history_on_dialpad_button:
402            case R.id.call_history_button:
403                // Use explicit CallLogActivity intent instead of ACTION_VIEW +
404                // CONTENT_TYPE, so that we always open our call log from our dialer
405                final Intent intent = new Intent(this, CallLogActivity.class);
406                startActivity(intent);
407                break;
408            case R.id.search_close_button:
409                // Clear the search field
410                if (!TextUtils.isEmpty(mSearchView.getText())) {
411                    mDialpadFragment.clearDialpad();
412                    mSearchView.setText("");
413                }
414                break;
415            case R.id.voice_search_button:
416                final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
417                startActivityForResult(voiceIntent, ACTIVITY_REQUEST_CODE_VOICE_SEARCH);
418                break;
419            default: {
420                Log.wtf(TAG, "Unexpected onClick event from " + view);
421                break;
422            }
423        }
424    }
425
426    @Override
427    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
428        if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) {
429            if (resultCode == RESULT_OK) {
430                final ArrayList<String> matches = data.getStringArrayListExtra(
431                        RecognizerIntent.EXTRA_RESULTS);
432                if (matches.size() > 0) {
433                    final String match = matches.get(0);
434                    mSearchView.setText(match);
435                } else {
436                    Log.e(TAG, "Voice search - nothing heard");
437                }
438            } else {
439                Log.e(TAG, "Voice search failed");
440            }
441        }
442        super.onActivityResult(requestCode, resultCode, data);
443    }
444
445    private void showDialpadFragment(boolean animate) {
446        mDialpadFragment.setAdjustTranslationForAnimation(animate);
447        final FragmentTransaction ft = getFragmentManager().beginTransaction();
448        if (animate) {
449            ft.setCustomAnimations(R.anim.slide_in, 0);
450        } else {
451            mDialpadFragment.setYFraction(0);
452        }
453        ft.show(mDialpadFragment);
454        ft.commit();
455    }
456
457    private void hideDialpadFragment(boolean animate, boolean clearDialpad) {
458        if (mDialpadFragment == null) return;
459        if (clearDialpad) {
460            mDialpadFragment.clearDialpad();
461        }
462        if (!mDialpadFragment.isVisible()) return;
463        mDialpadFragment.setAdjustTranslationForAnimation(animate);
464        final FragmentTransaction ft = getFragmentManager().beginTransaction();
465        if (animate) {
466            ft.setCustomAnimations(0, R.anim.slide_out);
467        }
468        ft.hide(mDialpadFragment);
469        ft.commit();
470    }
471
472    private void prepareSearchView() {
473        mSearchViewContainer = findViewById(R.id.search_view_container);
474        mSearchViewCloseButton = findViewById(R.id.search_close_button);
475        mSearchViewCloseButton.setOnClickListener(this);
476        mVoiceSearchButton = findViewById(R.id.voice_search_button);
477        mVoiceSearchButton.setOnClickListener(this);
478        mSearchView = (EditText) findViewById(R.id.search_view);
479        mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
480        mSearchView.setHint(getString(R.string.dialer_hint_find_contact));
481        mSearchView.setOnFocusChangeListener(new OnFocusChangeListener() {
482            @Override
483            public void onFocusChange(View view, boolean hasFocus) {
484                if (hasFocus) {
485                    showInputMethod(view.findFocus());
486                }
487            }
488        });
489    }
490
491    final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
492        @Override
493        public void onAnimationEnd(Animator animation) {
494            mSearchViewContainer.setVisibility(View.GONE);
495        }
496    };
497
498    private boolean getInSearchUi() {
499        return mInDialpadSearch || mInRegularSearch;
500    }
501
502    private void setNotInSearchUi() {
503        mInDialpadSearch = false;
504        mInRegularSearch = false;
505    }
506
507    private void hideDialpadAndSearchUi() {
508        mSearchView.setText(null);
509        hideDialpadFragment(false, true);
510    }
511
512    public void hideSearchBar() {
513       hideSearchBar(true);
514    }
515
516    public void hideSearchBar(boolean shiftView) {
517        if (shiftView) {
518            mSearchViewContainer.animate().cancel();
519            mSearchViewContainer.setAlpha(1);
520            mSearchViewContainer.setTranslationY(0);
521            mSearchViewContainer.animate().withLayer().alpha(0).translationY(-mSearchView.getHeight())
522                    .setDuration(200).setListener(mHideListener);
523
524            mFragmentsFrame.animate().withLayer()
525                    .translationY(-mSearchViewContainer.getHeight()).setDuration(200).setListener(
526                    new AnimatorListenerAdapter() {
527                        @Override
528                        public void onAnimationEnd(Animator animation) {
529                            mBottomPaddingView.setVisibility(View.VISIBLE);
530                                mFragmentsFrame.setTranslationY(0);
531                        }
532                    });
533        } else {
534            mSearchViewContainer.setTranslationY(-mSearchView.getHeight());
535        }
536    }
537
538    public void showSearchBar() {
539        mSearchViewContainer.animate().cancel();
540        mSearchViewContainer.setAlpha(0);
541        mSearchViewContainer.setTranslationY(-mSearchViewContainer.getHeight());
542        mSearchViewContainer.animate().withLayer().alpha(1).translationY(0).setDuration(200)
543                .setListener(new AnimatorListenerAdapter() {
544                    @Override
545                    public void onAnimationStart(Animator animation) {
546                        mSearchViewContainer.setVisibility(View.VISIBLE);
547                    }
548                });
549
550        mFragmentsFrame.setTranslationY(-mSearchViewContainer.getHeight());
551        mFragmentsFrame.animate().withLayer().translationY(0).setDuration(200)
552                .setListener(
553                        new AnimatorListenerAdapter() {
554                            @Override
555                            public void onAnimationStart(Animator animation) {
556                                mBottomPaddingView.setVisibility(View.GONE);
557                            }
558                        });
559    }
560
561
562    public void setupFakeActionBarItems() {
563        mMenuButton = findViewById(R.id.overflow_menu);
564        if (mMenuButton != null) {
565            mMenuButton.setOnClickListener(this);
566        }
567
568        mCallHistoryButton = findViewById(R.id.call_history_button);
569        // mCallHistoryButton.setMinimumWidth(fakeMenuItemWidth);
570        mCallHistoryButton.setOnClickListener(this);
571
572        mDialpadButton = findViewById(R.id.dialpad_button);
573        // DialpadButton.setMinimumWidth(fakeMenuItemWidth);
574        mDialpadButton.setOnClickListener(this);
575    }
576
577    public void setupFakeActionBarItemsForDialpadFragment() {
578        final View callhistoryButton = findViewById(R.id.call_history_on_dialpad_button);
579        callhistoryButton.setOnClickListener(this);
580    }
581
582    private void fixIntent(Intent intent) {
583        // This should be cleaned up: the call key used to send an Intent
584        // that just said to go to the recent calls list.  It now sends this
585        // abstract action, but this class hasn't been rewritten to deal with it.
586        if (Intent.ACTION_CALL_BUTTON.equals(intent.getAction())) {
587            intent.setDataAndType(Calls.CONTENT_URI, Calls.CONTENT_TYPE);
588            intent.putExtra("call_key", true);
589            setIntent(intent);
590        }
591    }
592
593    /**
594     * Returns true if the intent is due to hitting the green send key (hardware call button:
595     * KEYCODE_CALL) while in a call.
596     *
597     * @param intent the intent that launched this activity
598     * @param recentCallsRequest true if the intent is requesting to view recent calls
599     * @return true if the intent is due to hitting the green send key while in a call
600     */
601    private boolean isSendKeyWhileInCall(Intent intent, boolean recentCallsRequest) {
602        // If there is a call in progress go to the call screen
603        if (recentCallsRequest) {
604            final boolean callKey = intent.getBooleanExtra("call_key", false);
605
606            try {
607                ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
608                if (callKey && phone != null && phone.showCallScreen()) {
609                    return true;
610                }
611            } catch (RemoteException e) {
612                Log.e(TAG, "Failed to handle send while in call", e);
613            }
614        }
615
616        return false;
617    }
618
619    /**
620     * Sets the current tab based on the intent's request type
621     *
622     * @param intent Intent that contains information about which tab should be selected
623     */
624    private void displayFragment(Intent intent) {
625        // If we got here by hitting send and we're in call forward along to the in-call activity
626        boolean recentCallsRequest = Calls.CONTENT_TYPE.equals(intent.resolveType(
627            getContentResolver()));
628        if (isSendKeyWhileInCall(intent, recentCallsRequest)) {
629            finish();
630            return;
631        }
632
633        if (mDialpadFragment != null && (phoneIsInUse() || isDialIntent(intent))) {
634            mDialpadFragment.setStartedFromNewIntent(true);
635            showDialpadFragment(false);
636        }
637    }
638
639    @Override
640    public void onNewIntent(Intent newIntent) {
641        setIntent(newIntent);
642        fixIntent(newIntent);
643        displayFragment(newIntent);
644        final String action = newIntent.getAction();
645
646        invalidateOptionsMenu();
647    }
648
649    /** Returns true if the given intent contains a phone number to populate the dialer with */
650    private boolean isDialIntent(Intent intent) {
651        final String action = intent.getAction();
652        if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {
653            return true;
654        }
655        if (Intent.ACTION_VIEW.equals(action)) {
656            final Uri data = intent.getData();
657            if (data != null && CallUtil.SCHEME_TEL.equals(data.getScheme())) {
658                return true;
659            }
660        }
661        return false;
662    }
663
664    /**
665     * Returns an appropriate call origin for this Activity. May return null when no call origin
666     * should be used (e.g. when some 3rd party application launched the screen. Call origin is
667     * for remembering the tab in which the user made a phone call, so the external app's DIAL
668     * request should not be counted.)
669     */
670    public String getCallOrigin() {
671        return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null;
672    }
673
674    /**
675     * Retrieves the filter text stored in {@link #setupFilterText(Intent)}.
676     * This text originally came from a FILTER_CONTACTS_ACTION intent received
677     * by this activity. The stored text will then be cleared after after this
678     * method returns.
679     *
680     * @return The stored filter text
681     */
682    public String getAndClearFilterText() {
683        String filterText = mFilterText;
684        mFilterText = null;
685        return filterText;
686    }
687
688    /**
689     * Stores the filter text associated with a FILTER_CONTACTS_ACTION intent.
690     * This is so child activities can check if they are supposed to display a filter.
691     *
692     * @param intent The intent received in {@link #onNewIntent(Intent)}
693     */
694    private void setupFilterText(Intent intent) {
695        // If the intent was relaunched from history, don't apply the filter text.
696        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
697            return;
698        }
699        String filter = intent.getStringExtra(UI.FILTER_TEXT_EXTRA_KEY);
700        if (filter != null && filter.length() > 0) {
701            mFilterText = filter;
702        }
703    }
704
705    private final PhoneFavoriteFragment.Listener mPhoneFavoriteListener =
706            new PhoneFavoriteFragment.Listener() {
707        @Override
708        public void onContactSelected(Uri contactUri) {
709            PhoneNumberInteraction.startInteractionForPhoneCall(
710                        DialtactsActivity.this, contactUri, getCallOrigin());
711        }
712
713        @Override
714        public void onCallNumberDirectly(String phoneNumber) {
715            Intent intent = CallUtil.getCallIntent(phoneNumber, getCallOrigin());
716            startActivity(intent);
717        }
718    };
719
720    /* TODO krelease: This is only relevant for phones that have a hard button search key (i.e.
721     * Nexus S). Supporting it is a little more tricky because of the dialpad fragment might
722     * be showing when the search key is pressed so there is more state management involved.
723
724    @Override
725    public void startSearch(String initialQuery, boolean selectInitialQuery,
726            Bundle appSearchData, boolean globalSearch) {
727        if (mRegularSearchFragment != null && mRegularSearchFragment.isAdded() && !globalSearch) {
728            if (mInSearchUi) {
729                if (mSearchView.hasFocus()) {
730                    showInputMethod(mSearchView.findFocus());
731                } else {
732                    mSearchView.requestFocus();
733                }
734            } else {
735                enterSearchUi();
736            }
737        } else {
738            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
739        }
740    }*/
741
742    private void showInputMethod(View view) {
743        final InputMethodManager imm = (InputMethodManager) getSystemService(
744                Context.INPUT_METHOD_SERVICE);
745        if (imm != null) {
746            imm.showSoftInput(view, 0);
747        }
748    }
749
750    private void hideInputMethod(View view) {
751        final InputMethodManager imm = (InputMethodManager) getSystemService(
752                Context.INPUT_METHOD_SERVICE);
753        if (imm != null && view != null) {
754            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
755        }
756    }
757
758    /**
759     * Shows the search fragment
760     */
761    private void enterSearchUi(boolean smartDialSearch, String query) {
762        if (getFragmentManager().isDestroyed()) {
763            // Weird race condition where fragment is doing work after the activity is destroyed
764            // due to talkback being on (b/10209937). Just return since we can't do any
765            // constructive here.
766            return;
767        }
768
769        if (DEBUG) {
770            Log.d(TAG, "Entering search UI - smart dial " + smartDialSearch);
771        }
772
773        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
774        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
775
776        SearchFragment fragment;
777        if (mInDialpadSearch) {
778            transaction.remove(mSmartDialSearchFragment);
779        } else if (mInRegularSearch) {
780            transaction.remove(mRegularSearchFragment);
781        } else {
782            transaction.remove(mPhoneFavoriteFragment);
783        }
784
785        final String tag;
786        if (smartDialSearch) {
787            tag = TAG_SMARTDIAL_SEARCH_FRAGMENT;
788        } else {
789            tag = TAG_REGULAR_SEARCH_FRAGMENT;
790        }
791        mInDialpadSearch = smartDialSearch;
792        mInRegularSearch = !smartDialSearch;
793
794        fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag);
795        if (fragment == null) {
796            if (smartDialSearch) {
797                fragment = new SmartDialSearchFragment();
798            } else {
799                fragment = new RegularSearchFragment();
800            }
801        }
802        transaction.replace(R.id.dialtacts_frame, fragment, tag);
803        transaction.addToBackStack(null);
804        fragment.setQueryString(query, false);
805        transaction.commit();
806    }
807
808    /**
809     * Hides the search fragment
810     */
811    private void exitSearchUi() {
812        // Go all the way back to the favorites fragment, regardless of how many times we
813        // transitioned between search fragments
814        final BackStackEntry entry = getFragmentManager().getBackStackEntryAt(0);
815        getFragmentManager().popBackStack(0, FragmentManager.POP_BACK_STACK_INCLUSIVE);
816        setNotInSearchUi();
817    }
818
819    /** Returns an Intent to launch Call Settings screen */
820    public static Intent getCallSettingsIntent() {
821        final Intent intent = new Intent(Intent.ACTION_MAIN);
822        intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME);
823        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
824        return intent;
825    }
826
827    @Override
828    public void onBackPressed() {
829        if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
830            hideDialpadFragment(true, false);
831        } else if (getInSearchUi()) {
832            mSearchView.setText(null);
833            mDialpadFragment.clearDialpad();
834        } else if (isTaskRoot()) {
835            // Instead of stopping, simply push this to the back of the stack.
836            // This is only done when running at the top of the stack;
837            // otherwise, we have been launched by someone else so need to
838            // allow the user to go back to the caller.
839            moveTaskToBack(false);
840        } else {
841            super.onBackPressed();
842        }
843    }
844
845    @Override
846    public void onDialpadQueryChanged(String query) {
847        final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,
848                SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
849        if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
850            if (DEBUG) {
851                Log.d(TAG, "onDialpadQueryChanged - new query: " + query);
852            }
853            if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
854                // This callback can happen if the dialpad fragment is recreated because of
855                // activity destruction. In that case, don't update the search view because
856                // that would bring the user back to the search fragment regardless of the
857                // previous state of the application. Instead, just return here and let the
858                // fragment manager correctly figure out whatever fragment was last displayed.
859                return;
860            }
861            mSearchView.setText(normalizedQuery);
862        }
863    }
864
865    @Override
866    public void onListFragmentScrollStateChange(int scrollState) {
867        if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
868            hideDialpadFragment(true, false);
869            hideInputMethod(getCurrentFocus());
870        }
871    }
872
873    @Override
874    public void onDialpadFragmentStarted() {
875        setupFakeActionBarItemsForDialpadFragment();
876    }
877
878    private boolean phoneIsInUse() {
879        final TelephonyManager tm = (TelephonyManager) getSystemService(
880                Context.TELEPHONY_SERVICE);
881        return tm.getCallState() != TelephonyManager.CALL_STATE_IDLE;
882    }
883
884    @Override
885    public void onShowAllContacts() {
886        final Intent intent = new Intent(this, AllContactsActivity.class);
887        startActivity(intent);
888    }
889
890    public static Intent getAddNumberToContactIntent(CharSequence text) {
891        final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
892        intent.putExtra(Intents.Insert.PHONE, text);
893        intent.setType(Contacts.CONTENT_ITEM_TYPE);
894        return intent;
895    }
896
897    public static Intent getInsertContactWithNameIntent(CharSequence text) {
898        final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
899        intent.putExtra(Intents.Insert.NAME, text);
900        return intent;
901    }
902}
903