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