DialtactsActivity.java revision 4b25da79091157935042d2942a8961ceba92166f
1/*
2 * Copyright (C) 2008 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.contacts.activities;
18
19import com.android.contacts.R;
20import com.android.contacts.calllog.CallLogFragment;
21import com.android.contacts.dialpad.DialpadFragment;
22import com.android.contacts.interactions.PhoneNumberInteraction;
23import com.android.contacts.list.AccountFilterActivity;
24import com.android.contacts.list.ContactListFilter;
25import com.android.contacts.list.ContactListFilterController;
26import com.android.contacts.list.ContactListFilterController.ContactListFilterListener;
27import com.android.contacts.list.ContactTileAdapter.DisplayType;
28import com.android.contacts.list.ContactTileListFragment;
29import com.android.contacts.list.OnPhoneNumberPickerActionListener;
30import com.android.contacts.list.PhoneNumberPickerFragment;
31import com.android.internal.telephony.ITelephony;
32
33import android.app.ActionBar;
34import android.app.ActionBar.LayoutParams;
35import android.app.ActionBar.Tab;
36import android.app.ActionBar.TabListener;
37import android.app.Activity;
38import android.app.Fragment;
39import android.app.FragmentManager;
40import android.app.FragmentTransaction;
41import android.content.Context;
42import android.content.Intent;
43import android.content.SharedPreferences;
44import android.net.Uri;
45import android.os.Bundle;
46import android.os.RemoteException;
47import android.os.ServiceManager;
48import android.provider.CallLog.Calls;
49import android.provider.ContactsContract.Contacts;
50import android.provider.ContactsContract.Intents.UI;
51import android.support.v13.app.FragmentPagerAdapter;
52import android.support.v4.view.ViewPager;
53import android.support.v4.view.ViewPager.OnPageChangeListener;
54import android.text.TextUtils;
55import android.util.Log;
56import android.view.Menu;
57import android.view.MenuInflater;
58import android.view.MenuItem;
59import android.view.MenuItem.OnMenuItemClickListener;
60import android.view.View;
61import android.view.View.OnClickListener;
62import android.view.View.OnFocusChangeListener;
63import android.view.ViewConfiguration;
64import android.view.inputmethod.InputMethodManager;
65import android.widget.PopupMenu;
66import android.widget.SearchView;
67import android.widget.SearchView.OnCloseListener;
68import android.widget.SearchView.OnQueryTextListener;
69
70/**
71 * The dialer activity that has one tab with the virtual 12key
72 * dialer, a tab with recent calls in it, a tab with the contacts and
73 * a tab with the favorite. This is the container and the tabs are
74 * embedded using intents.
75 * The dialer tab's title is 'phone', a more common name (see strings.xml).
76 */
77public class DialtactsActivity extends Activity {
78    private static final String TAG = "DialtactsActivity";
79
80    /** Used to open Call Setting */
81    private static final String PHONE_PACKAGE = "com.android.phone";
82    private static final String CALL_SETTINGS_CLASS_NAME =
83            "com.android.phone.CallFeaturesSetting";
84
85    /**
86     * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}.
87     */
88    private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER";
89
90    /** Used both by {@link ActionBar} and {@link ViewPagerAdapter} */
91    private static final int TAB_INDEX_DIALER = 0;
92    private static final int TAB_INDEX_CALL_LOG = 1;
93    private static final int TAB_INDEX_FAVORITES = 2;
94
95    private static final int TAB_INDEX_COUNT = 3;
96
97    private static final int SUBACTIVITY_ACCOUNT_FILTER = 0;
98
99    /** Name of the dialtacts shared preferences */
100    static final String PREFS_DIALTACTS = "dialtacts";
101    static final boolean PREF_FAVORITES_AS_CONTACTS_DEFAULT = false;
102
103    /** Last manually selected tab index */
104    private static final String PREF_LAST_MANUALLY_SELECTED_TAB = "last_manually_selected_tab";
105    private static final int PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT = TAB_INDEX_DIALER;
106
107    /**
108     * Listener interface for Fragments accommodated in {@link ViewPager} enabling them to know
109     * when it becomes visible or invisible inside the ViewPager.
110     */
111    public interface ViewPagerVisibilityListener {
112        public void onVisibilityChanged(boolean visible);
113    }
114
115    public class ViewPagerAdapter extends FragmentPagerAdapter {
116        private DialpadFragment mDialpadFragment;
117        private CallLogFragment mCallLogFragment;
118        private ContactTileListFragment mContactTileListFragment;
119
120        public ViewPagerAdapter(FragmentManager fm) {
121            super(fm);
122        }
123
124        @Override
125        public Fragment getItem(int position) {
126            switch (position) {
127                case TAB_INDEX_DIALER:
128                    if (mDialpadFragment == null) {
129                        mDialpadFragment = new DialpadFragment();
130                    }
131                    return mDialpadFragment;
132                case TAB_INDEX_CALL_LOG:
133                    if (mCallLogFragment == null) {
134                        mCallLogFragment = new CallLogFragment();
135                    }
136                    return mCallLogFragment;
137                case TAB_INDEX_FAVORITES:
138                    if (mContactTileListFragment == null) {
139                        mContactTileListFragment = new ContactTileListFragment();
140                    }
141                    return mContactTileListFragment;
142            }
143            throw new IllegalStateException("No fragment at position " + position);
144        }
145
146        @Override
147        public int getCount() {
148            return TAB_INDEX_COUNT;
149        }
150    }
151
152    private class PageChangeListener implements OnPageChangeListener {
153        private int mCurrentPosition = -1;
154        /**
155         * Used during page migration, to remember the next position {@link #onPageSelected(int)}
156         * specified.
157         */
158        private int mNextPosition = -1;
159
160        @Override
161        public void onPageScrolled(
162                int position, float positionOffset, int positionOffsetPixels) {
163        }
164
165        @Override
166        public void onPageSelected(int position) {
167            final ActionBar actionBar = getActionBar();
168            if (mCurrentPosition == position) {
169                Log.w(TAG, "Previous position and next position became same (" + position + ")");
170            }
171
172            actionBar.selectTab(actionBar.getTabAt(position));
173            mNextPosition = position;
174        }
175
176        public void setCurrentPosition(int position) {
177            mCurrentPosition = position;
178        }
179
180        @Override
181        public void onPageScrollStateChanged(int state) {
182            switch (state) {
183                case ViewPager.SCROLL_STATE_IDLE: {
184                    if (mCurrentPosition >= 0) {
185                        sendFragmentVisibilityChange(mCurrentPosition, false);
186                    }
187                    if (mNextPosition >= 0) {
188                        sendFragmentVisibilityChange(mNextPosition, true);
189                    }
190                    invalidateOptionsMenu();
191
192                    mCurrentPosition = mNextPosition;
193                    break;
194                }
195                case ViewPager.SCROLL_STATE_DRAGGING:
196                case ViewPager.SCROLL_STATE_SETTLING:
197                default:
198                    break;
199            }
200        }
201    }
202
203    private String mFilterText;
204    private Uri mDialUri;
205
206    /** Enables horizontal swipe between Fragments. */
207    private ViewPager mViewPager;
208    private final PageChangeListener mPageChangeListener = new PageChangeListener();
209    private DialpadFragment mDialpadFragment;
210    private CallLogFragment mCallLogFragment;
211    private ContactTileListFragment mStrequentFragment;
212
213    private final TabListener mTabListener = new TabListener() {
214        @Override
215        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
216        }
217
218        @Override
219        public void onTabSelected(Tab tab, FragmentTransaction ft) {
220            if (mViewPager.getCurrentItem() != tab.getPosition()) {
221                mViewPager.setCurrentItem(tab.getPosition(), true);
222            }
223
224            // During the call, we don't remember the tab position.
225            if (!DialpadFragment.phoneIsInUse()) {
226                // Remember this tab index. This function is also called, if the tab is set
227                // automatically in which case the setter (setCurrentTab) has to set this to its old
228                // value afterwards
229                mLastManuallySelectedFragment = tab.getPosition();
230            }
231        }
232
233        @Override
234        public void onTabReselected(Tab tab, FragmentTransaction ft) {
235        }
236    };
237
238    /**
239     * Fragment for searching phone numbers. Unlike the other Fragments, this doesn't correspond
240     * to tab but is shown by a search action.
241     */
242    private PhoneNumberPickerFragment mSearchFragment;
243    /**
244     * True when this Activity is in its search UI (with a {@link SearchView} and
245     * {@link PhoneNumberPickerFragment}).
246     */
247    private boolean mInSearchUi;
248    private SearchView mSearchView;
249
250    private final OnClickListener mFilterOptionClickListener = new OnClickListener() {
251        @Override
252        public void onClick(View view) {
253            final PopupMenu popupMenu = new PopupMenu(DialtactsActivity.this, view);
254            final Menu menu = popupMenu.getMenu();
255            popupMenu.inflate(R.menu.dialtacts_search_options);
256            final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
257            filterOptionMenuItem.setOnMenuItemClickListener(mFilterOptionsMenuItemClickListener);
258            final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
259            addContactOptionMenuItem.setIntent(
260                    new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
261            popupMenu.show();
262        }
263    };
264
265    /**
266     * The index of the Fragment (or, the tab) that has last been manually selected.
267     * This value does not keep track of programmatically set Tabs (e.g. Call Log after a Call)
268     */
269    private int mLastManuallySelectedFragment;
270
271    private ContactListFilterController mContactListFilterController;
272    private OnMenuItemClickListener mFilterOptionsMenuItemClickListener =
273            new OnMenuItemClickListener() {
274        @Override
275        public boolean onMenuItemClick(MenuItem item) {
276            final Intent intent =
277                    new Intent(DialtactsActivity.this, AccountFilterActivity.class);
278            ContactListFilter filter = mContactListFilterController.getFilter();
279            startActivityForResult(intent, SUBACTIVITY_ACCOUNT_FILTER);
280            return true;
281        }
282    };
283
284    private OnMenuItemClickListener mSearchMenuItemClickListener =
285            new OnMenuItemClickListener() {
286        @Override
287        public boolean onMenuItemClick(MenuItem item) {
288            enterSearchUi();
289            return true;
290        }
291    };
292
293    /**
294     * Listener used when one of phone numbers in search UI is selected. This will initiate a
295     * phone call using the phone number.
296     */
297    private final OnPhoneNumberPickerActionListener mPhoneNumberPickerActionListener =
298            new OnPhoneNumberPickerActionListener() {
299                @Override
300                public void onPickPhoneNumberAction(Uri dataUri) {
301                    PhoneNumberInteraction.startInteractionForPhoneCall(
302                            DialtactsActivity.this, dataUri);
303                }
304
305                @Override
306                public void onShortcutIntentCreated(Intent intent) {
307                    Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring.");
308                }
309
310                @Override
311                public void onHomeInActionBarSelected() {
312                    exitSearchUi();
313                }
314    };
315
316    /**
317     * Listener used to send search queries to the phone search fragment.
318     */
319    private final OnQueryTextListener mPhoneSearchQueryTextListener =
320            new OnQueryTextListener() {
321                @Override
322                public boolean onQueryTextSubmit(String query) {
323                    View view = getCurrentFocus();
324                    if (view != null) {
325                        hideInputMethod(view);
326                        view.clearFocus();
327                    }
328                    return true;
329                }
330
331                @Override
332                public boolean onQueryTextChange(String newText) {
333                    // Show search result with non-empty text. Show a bare list otherwise.
334                    mSearchFragment.setQueryString(newText, true);
335                    mSearchFragment.setSearchMode(!TextUtils.isEmpty(newText));
336                    return true;
337                }
338    };
339
340    /**
341     * Listener used to handle the "close" button on the right side of {@link SearchView}.
342     * If some text is in the search view, this will clean it up. Otherwise this will exit
343     * the search UI and let users go back to usual Phone UI.
344     *
345     * This does _not_ handle back button.
346     */
347    private final OnCloseListener mPhoneSearchCloseListener =
348            new OnCloseListener() {
349                @Override
350                public boolean onClose() {
351                    if (!TextUtils.isEmpty(mSearchView.getQuery())) {
352                        mSearchView.setQuery(null, true);
353                    }
354                    return true;
355                }
356    };
357
358    @Override
359    protected void onCreate(Bundle icicle) {
360        super.onCreate(icicle);
361
362        final Intent intent = getIntent();
363        fixIntent(intent);
364
365        setContentView(R.layout.dialtacts_activity);
366
367        mContactListFilterController = new ContactListFilterController(this);
368        mContactListFilterController.addListener(new ContactListFilterListener() {
369            @Override
370            public void onContactListFilterChanged() {
371                if (mSearchFragment == null || !mSearchFragment.isAdded()) {
372                    Log.w(TAG, "Search Fragment isn't available when ContactListFilter is changed");
373                    return;
374                }
375                mSearchFragment.setFilter(mContactListFilterController.getFilter());
376
377                invalidateOptionsMenu();
378            }
379        });
380
381        mViewPager = (ViewPager) findViewById(R.id.pager);
382        mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
383        mViewPager.setOnPageChangeListener(mPageChangeListener);
384
385        prepareSearchView();
386
387        // Setup the ActionBar tabs (the order matches the tab-index contants TAB_INDEX_*)
388        setupDialer();
389        setupCallLog();
390        setupFavorites();
391        getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
392        getActionBar().setDisplayShowTitleEnabled(false);
393        getActionBar().setDisplayShowHomeEnabled(false);
394
395        // Load the last manually loaded tab
396        final SharedPreferences prefs = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE);
397        mLastManuallySelectedFragment = prefs.getInt(PREF_LAST_MANUALLY_SELECTED_TAB,
398                PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT);
399        if (mLastManuallySelectedFragment >= TAB_INDEX_COUNT) {
400            // Stored value may have exceeded the number of current tabs. Reset it.
401            mLastManuallySelectedFragment = PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT;
402        }
403
404        setCurrentTab(intent);
405
406        if (UI.FILTER_CONTACTS_ACTION.equals(intent.getAction())
407                && icicle == null) {
408            setupFilterText(intent);
409        }
410    }
411
412    private void prepareSearchView() {
413        final View searchViewLayout =
414                getLayoutInflater().inflate(R.layout.dialtacts_custom_action_bar, null);
415        mSearchView = (SearchView) searchViewLayout.findViewById(R.id.search_view);
416        mSearchView.setOnQueryTextListener(mPhoneSearchQueryTextListener);
417        mSearchView.setOnCloseListener(mPhoneSearchCloseListener);
418        // Since we're using a custom layout for showing SearchView instead of letting the
419        // search menu icon do that job, we need to manually configure the View so it looks
420        // "shown via search menu".
421        // - it should be iconified by default
422        // - it should not be iconified at this time
423        // See also comments for onActionViewExpanded()/onActionViewCollapsed()
424        mSearchView.setIconifiedByDefault(true);
425        mSearchView.setQueryHint(getString(R.string.hint_findContacts));
426        mSearchView.setIconified(false);
427        mSearchView.setOnQueryTextFocusChangeListener(new OnFocusChangeListener() {
428            @Override
429            public void onFocusChange(View view, boolean hasFocus) {
430                if (hasFocus) {
431                    showInputMethod(view.findFocus());
432                }
433            }
434        });
435
436        if (!ViewConfiguration.get(this).hasPermanentMenuKey()) {
437            // Filter option menu should be shown on the right side of SearchView.
438            final View filterOptionView = searchViewLayout.findViewById(R.id.search_option);
439            filterOptionView.setVisibility(View.VISIBLE);
440            filterOptionView.setOnClickListener(mFilterOptionClickListener);
441        }
442
443        getActionBar().setCustomView(searchViewLayout,
444                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
445    }
446
447    @Override
448    public void onAttachFragment(Fragment fragment) {
449        // This method can be called before onCreate(), at which point we cannot rely on ViewPager.
450        // In that case, we will setup the "current position" soon after the ViewPager is ready.
451        final int currentPosition = mViewPager != null ? mViewPager.getCurrentItem() : -1;
452
453        if (fragment instanceof DialpadFragment) {
454            mDialpadFragment = (DialpadFragment) fragment;
455            mDialpadFragment.setListener(mDialpadListener);
456            if (currentPosition == TAB_INDEX_DIALER) {
457                mDialpadFragment.onVisibilityChanged(true);
458            }
459        } else if (fragment instanceof CallLogFragment) {
460            mCallLogFragment = (CallLogFragment) fragment;
461            if (currentPosition == TAB_INDEX_CALL_LOG) {
462                mCallLogFragment.onVisibilityChanged(true);
463            }
464        } else if (fragment instanceof ContactTileListFragment) {
465            mStrequentFragment = (ContactTileListFragment) fragment;
466            mStrequentFragment.enableQuickContact(false);
467            mStrequentFragment.setListener(mStrequentListener);
468            mStrequentFragment.setDisplayType(DisplayType.STREQUENT_PHONE_ONLY);
469        } else if (fragment instanceof PhoneNumberPickerFragment) {
470            mSearchFragment = (PhoneNumberPickerFragment) fragment;
471            mSearchFragment.setOnPhoneNumberPickerActionListener(mPhoneNumberPickerActionListener);
472            mSearchFragment.setQuickContactEnabled(true);
473            final FragmentTransaction transaction = getFragmentManager().beginTransaction();
474            if (mInSearchUi) {
475                transaction.show(mSearchFragment);
476            } else {
477                transaction.hide(mSearchFragment);
478            }
479            transaction.commitAllowingStateLoss();
480        }
481    }
482
483    @Override
484    protected void onPause() {
485        super.onPause();
486
487        final SharedPreferences.Editor editor =
488                getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE).edit();
489        editor.putInt(PREF_LAST_MANUALLY_SELECTED_TAB, mLastManuallySelectedFragment);
490
491        editor.apply();
492    }
493
494    private void fixIntent(Intent intent) {
495        // This should be cleaned up: the call key used to send an Intent
496        // that just said to go to the recent calls list.  It now sends this
497        // abstract action, but this class hasn't been rewritten to deal with it.
498        if (Intent.ACTION_CALL_BUTTON.equals(intent.getAction())) {
499            intent.setDataAndType(Calls.CONTENT_URI, Calls.CONTENT_TYPE);
500            intent.putExtra("call_key", true);
501            setIntent(intent);
502        }
503    }
504
505    private void setupDialer() {
506        final Tab tab = getActionBar().newTab();
507        // TODO: Temporarily disable tab text labels (in all 4 tabs in this
508        //   activity) so that the current tabs will all fit onscreen in
509        //   portrait (bug 4520620).  (Also note we do setText("") rather
510        //   leaving the text null, to work around bug 4521549.)
511        tab.setText("");  // R.string.dialerIconLabel
512        tab.setTabListener(mTabListener);
513        tab.setIcon(R.drawable.ic_tab_dialer);
514        getActionBar().addTab(tab);
515    }
516
517    private void setupCallLog() {
518        final Tab tab = getActionBar().newTab();
519        tab.setText("");  // R.string.recentCallsIconLabel
520        tab.setIcon(R.drawable.ic_tab_recent);
521        tab.setTabListener(mTabListener);
522        getActionBar().addTab(tab);
523    }
524
525    private void setupFavorites() {
526        final Tab tab = getActionBar().newTab();
527        tab.setText("");  // R.string.contactsFavoritesLabel
528        tab.setIcon(R.drawable.ic_tab_starred);
529        tab.setTabListener(mTabListener);
530        getActionBar().addTab(tab);
531    }
532
533    /**
534     * Returns true if the intent is due to hitting the green send key while in a call.
535     *
536     * @param intent the intent that launched this activity
537     * @param recentCallsRequest true if the intent is requesting to view recent calls
538     * @return true if the intent is due to hitting the green send key while in a call
539     */
540    private boolean isSendKeyWhileInCall(final Intent intent,
541            final boolean recentCallsRequest) {
542        // If there is a call in progress go to the call screen
543        if (recentCallsRequest) {
544            final boolean callKey = intent.getBooleanExtra("call_key", false);
545
546            try {
547                ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
548                if (callKey && phone != null && phone.showCallScreen()) {
549                    return true;
550                }
551            } catch (RemoteException e) {
552                Log.e(TAG, "Failed to handle send while in call", e);
553            }
554        }
555
556        return false;
557    }
558
559    /**
560     * Sets the current tab based on the intent's request type
561     *
562     * @param intent Intent that contains information about which tab should be selected
563     */
564    private void setCurrentTab(Intent intent) {
565        // If we got here by hitting send and we're in call forward along to the in-call activity
566        final boolean recentCallsRequest = Calls.CONTENT_TYPE.equals(intent.getType());
567        if (isSendKeyWhileInCall(intent, recentCallsRequest)) {
568            finish();
569            return;
570        }
571
572        // Remember the old manually selected tab index so that it can be restored if it is
573        // overwritten by one of the programmatic tab selections
574        final int savedTabIndex = mLastManuallySelectedFragment;
575
576        final int tabIndex;
577        if (DialpadFragment.phoneIsInUse() || isDialIntent(intent)) {
578            tabIndex = TAB_INDEX_DIALER;
579        } else if (recentCallsRequest) {
580            tabIndex = TAB_INDEX_CALL_LOG;
581        } else {
582            tabIndex = mLastManuallySelectedFragment;
583        }
584
585        final int previousItemIndex = mViewPager.getCurrentItem();
586        mViewPager.setCurrentItem(tabIndex, false /* smoothScroll */);
587        if (previousItemIndex != tabIndex) {
588            sendFragmentVisibilityChange(previousItemIndex, false);
589        }
590        mPageChangeListener.setCurrentPosition(tabIndex);
591        sendFragmentVisibilityChange(tabIndex, true);
592
593        // Restore to the previous manual selection
594        mLastManuallySelectedFragment = savedTabIndex;
595    }
596
597    @Override
598    public void onNewIntent(Intent newIntent) {
599        setIntent(newIntent);
600        fixIntent(newIntent);
601        setCurrentTab(newIntent);
602        final String action = newIntent.getAction();
603        if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
604            setupFilterText(newIntent);
605        }
606        if (mInSearchUi || mSearchFragment.isVisible()) {
607            exitSearchUi();
608        }
609
610        if (mViewPager.getCurrentItem() == TAB_INDEX_DIALER) {
611            if (mDialpadFragment != null) {
612                mDialpadFragment.configureScreenFromIntent(newIntent);
613            } else {
614                Log.e(TAG, "DialpadFragment isn't ready yet when the tab is already selected.");
615            }
616        }
617        invalidateOptionsMenu();
618    }
619
620    /** Returns true if the given intent contains a phone number to populate the dialer with */
621    private boolean isDialIntent(Intent intent) {
622        final String action = intent.getAction();
623        if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {
624            return true;
625        }
626        if (Intent.ACTION_VIEW.equals(action)) {
627            final Uri data = intent.getData();
628            if (data != null && "tel".equals(data.getScheme())) {
629                return true;
630            }
631        }
632        return false;
633    }
634
635    /**
636     * Retrieves the filter text stored in {@link #setupFilterText(Intent)}.
637     * This text originally came from a FILTER_CONTACTS_ACTION intent received
638     * by this activity. The stored text will then be cleared after after this
639     * method returns.
640     *
641     * @return The stored filter text
642     */
643    public String getAndClearFilterText() {
644        String filterText = mFilterText;
645        mFilterText = null;
646        return filterText;
647    }
648
649    /**
650     * Stores the filter text associated with a FILTER_CONTACTS_ACTION intent.
651     * This is so child activities can check if they are supposed to display a filter.
652     *
653     * @param intent The intent received in {@link #onNewIntent(Intent)}
654     */
655    private void setupFilterText(Intent intent) {
656        // If the intent was relaunched from history, don't apply the filter text.
657        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
658            return;
659        }
660        String filter = intent.getStringExtra(UI.FILTER_TEXT_EXTRA_KEY);
661        if (filter != null && filter.length() > 0) {
662            mFilterText = filter;
663        }
664    }
665
666    @Override
667    public void onBackPressed() {
668        if (mInSearchUi) {
669            // We should let the user go back to usual screens with tabs.
670            exitSearchUi();
671        } else if (isTaskRoot()) {
672            // Instead of stopping, simply push this to the back of the stack.
673            // This is only done when running at the top of the stack;
674            // otherwise, we have been launched by someone else so need to
675            // allow the user to go back to the caller.
676            moveTaskToBack(false);
677        } else {
678            super.onBackPressed();
679        }
680    }
681
682    private DialpadFragment.Listener mDialpadListener = new DialpadFragment.Listener() {
683        @Override
684        public void onSearchButtonPressed() {
685            enterSearchUi();
686        }
687    };
688
689    private ContactTileListFragment.Listener mStrequentListener =
690            new ContactTileListFragment.Listener() {
691        @Override
692        public void onContactSelected(Uri contactUri) {
693            PhoneNumberInteraction.startInteractionForPhoneCall(
694                    DialtactsActivity.this, contactUri);
695        }
696    };
697
698    @Override
699    public boolean onCreateOptionsMenu(Menu menu) {
700        MenuInflater inflater = getMenuInflater();
701        inflater.inflate(R.menu.dialtacts_options, menu);
702        return true;
703    }
704
705    @Override
706    public boolean onPrepareOptionsMenu(Menu menu) {
707        final MenuItem searchMenuItem = menu.findItem(R.id.search_on_action_bar);
708        final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
709        final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
710        final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings);
711        Tab tab = getActionBar().getSelectedTab();
712        if (mInSearchUi) {
713            searchMenuItem.setVisible(false);
714            if (ViewConfiguration.get(this).hasPermanentMenuKey()) {
715                filterOptionMenuItem.setVisible(true);
716                filterOptionMenuItem.setOnMenuItemClickListener(
717                        mFilterOptionsMenuItemClickListener);
718                addContactOptionMenuItem.setVisible(true);
719                addContactOptionMenuItem.setIntent(
720                        new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
721            } else {
722                // Filter option menu should be not be shown as a overflow menu.
723                filterOptionMenuItem.setVisible(false);
724                addContactOptionMenuItem.setVisible(false);
725            }
726            callSettingsMenuItem.setVisible(false);
727        } else {
728            final boolean showCallSettingsMenu;
729            if (tab != null && tab.getPosition() == TAB_INDEX_DIALER) {
730                searchMenuItem.setVisible(false);
731                // When permanent menu key is _not_ available, the call settings menu should be
732                // available via DialpadFragment.
733                showCallSettingsMenu = ViewConfiguration.get(this).hasPermanentMenuKey();
734            } else {
735                searchMenuItem.setVisible(true);
736                searchMenuItem.setOnMenuItemClickListener(mSearchMenuItemClickListener);
737                showCallSettingsMenu = true;
738            }
739            filterOptionMenuItem.setVisible(false);
740            addContactOptionMenuItem.setVisible(false);
741
742            if (showCallSettingsMenu) {
743                callSettingsMenuItem.setVisible(true);
744                callSettingsMenuItem.setIntent(DialtactsActivity.getCallSettingsIntent());
745            } else {
746                callSettingsMenuItem.setVisible(false);
747            }
748        }
749
750        return true;
751    }
752
753    @Override
754    public void startSearch(String initialQuery, boolean selectInitialQuery,
755            Bundle appSearchData, boolean globalSearch) {
756        if (mSearchFragment != null && mSearchFragment.isAdded() && !globalSearch) {
757            if (mInSearchUi) {
758                if (mSearchView.hasFocus()) {
759                    showInputMethod(mSearchView.findFocus());
760                } else {
761                    mSearchView.requestFocus();
762                }
763            } else {
764                enterSearchUi();
765            }
766        } else {
767            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
768        }
769    }
770
771    /**
772     * Hides every tab and shows search UI for phone lookup.
773     */
774    private void enterSearchUi() {
775        final ActionBar actionBar = getActionBar();
776
777        final Tab tab = actionBar.getSelectedTab();
778
779        // User can search during the call, but we don't want to remember the status.
780        if (tab != null && !DialpadFragment.phoneIsInUse()) {
781            mLastManuallySelectedFragment = tab.getPosition();
782        }
783
784        mSearchView.setQuery(null, true);
785
786        actionBar.setDisplayShowCustomEnabled(true);
787        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
788        actionBar.setDisplayShowHomeEnabled(true);
789        actionBar.setDisplayHomeAsUpEnabled(true);
790
791        sendFragmentVisibilityChange(mViewPager.getCurrentItem(), false);
792
793        // Show the search fragment and hide everything else.
794        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
795        transaction.show(mSearchFragment);
796        transaction.commitAllowingStateLoss();
797        mViewPager.setVisibility(View.GONE);
798
799        // We need to call this and onActionViewCollapsed() manually, since we are using a custom
800        // layout instead of asking the search menu item to take care of SearchView.
801        mSearchView.onActionViewExpanded();
802        mInSearchUi = true;
803
804        // Clear focus and suppress keyboard show-up.
805        mSearchView.clearFocus();
806    }
807
808    private void showInputMethod(View view) {
809        InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
810        if (imm != null) {
811            if (!imm.showSoftInput(view, 0)) {
812                Log.w(TAG, "Failed to show soft input method.");
813            }
814        }
815    }
816
817    private void hideInputMethod(View view) {
818        InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
819        if (imm != null && view != null) {
820            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
821        }
822    }
823
824    /**
825     * Goes back to usual Phone UI with tags. Previously selected Tag and associated Fragment
826     * should be automatically focused again.
827     */
828    private void exitSearchUi() {
829        final ActionBar actionBar = getActionBar();
830
831        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
832        transaction.hide(mSearchFragment);
833        transaction.commitAllowingStateLoss();
834
835        // We want to hide SearchView and show Tabs. Also focus on previously selected one.
836        actionBar.setDisplayShowCustomEnabled(false);
837        actionBar.setDisplayShowHomeEnabled(false);
838        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
839
840        sendFragmentVisibilityChange(mViewPager.getCurrentItem(), true);
841
842        mViewPager.setVisibility(View.VISIBLE);
843
844        hideInputMethod(getCurrentFocus());
845
846        // Request to update option menu.
847        invalidateOptionsMenu();
848
849        // See comments in onActionViewExpanded()
850        mSearchView.onActionViewCollapsed();
851        mInSearchUi = false;
852    }
853
854    private Fragment getFragmentAt(int position) {
855        switch (position) {
856            case TAB_INDEX_DIALER:
857                return mDialpadFragment;
858            case TAB_INDEX_CALL_LOG:
859                return mCallLogFragment;
860            case TAB_INDEX_FAVORITES:
861                return mStrequentFragment;
862            default:
863                throw new IllegalStateException("Unknown fragment index: " + position);
864        }
865    }
866
867    private void sendFragmentVisibilityChange(int position, boolean visibility) {
868        final Fragment fragment = getFragmentAt(position);
869        if (fragment instanceof ViewPagerVisibilityListener) {
870            ((ViewPagerVisibilityListener) fragment).onVisibilityChanged(visibility);
871        }
872    }
873
874    /** Returns an Intent to launch Call Settings screen */
875    public static Intent getCallSettingsIntent() {
876        final Intent intent = new Intent(Intent.ACTION_MAIN);
877        intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME);
878        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
879        return intent;
880    }
881
882    @Override
883    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
884        if (resultCode != Activity.RESULT_OK) {
885            return;
886        }
887        switch (requestCode) {
888            case SUBACTIVITY_ACCOUNT_FILTER: {
889                ContactListFilter filter = (ContactListFilter) data.getParcelableExtra(
890                        AccountFilterActivity.KEY_EXTRA_CONTACT_LIST_FILTER);
891                if (filter == null) {
892                    return;
893                }
894                if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
895                    mContactListFilterController.selectCustomFilter();
896                } else {
897                    mContactListFilterController.setContactListFilter(filter, true);
898                }
899            }
900            break;
901        }
902    }
903}
904