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.ContactListFilterController;
24import com.android.contacts.list.ContactListFilterController.ContactListFilterListener;
25import com.android.contacts.list.ContactListItemView;
26import com.android.contacts.list.OnPhoneNumberPickerActionListener;
27import com.android.contacts.list.PhoneFavoriteFragment;
28import com.android.contacts.list.PhoneNumberPickerFragment;
29import com.android.contacts.activities.TransactionSafeActivity;
30import com.android.contacts.util.AccountFilterUtil;
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.preference.PreferenceManager;
49import android.provider.CallLog.Calls;
50import android.provider.ContactsContract.Contacts;
51import android.provider.ContactsContract.Intents.UI;
52import android.support.v13.app.FragmentPagerAdapter;
53import android.support.v4.view.ViewPager;
54import android.support.v4.view.ViewPager.OnPageChangeListener;
55import android.text.TextUtils;
56import android.util.Log;
57import android.view.Menu;
58import android.view.MenuInflater;
59import android.view.MenuItem;
60import android.view.MenuItem.OnMenuItemClickListener;
61import android.view.View;
62import android.view.View.OnClickListener;
63import android.view.View.OnFocusChangeListener;
64import android.view.ViewConfiguration;
65import android.view.inputmethod.InputMethodManager;
66import android.widget.PopupMenu;
67import android.widget.SearchView;
68import android.widget.SearchView.OnCloseListener;
69import android.widget.SearchView.OnQueryTextListener;
70
71/**
72 * The dialer activity that has one tab with the virtual 12key
73 * dialer, a tab with recent calls in it, a tab with the contacts and
74 * a tab with the favorite. This is the container and the tabs are
75 * embedded using intents.
76 * The dialer tab's title is 'phone', a more common name (see strings.xml).
77 */
78public class DialtactsActivity extends TransactionSafeActivity {
79    private static final String TAG = "DialtactsActivity";
80
81    private static final boolean DEBUG = false;
82
83    /** Used to open Call Setting */
84    private static final String PHONE_PACKAGE = "com.android.phone";
85    private static final String CALL_SETTINGS_CLASS_NAME =
86            "com.android.phone.CallFeaturesSetting";
87
88    /**
89     * Copied from PhoneApp. See comments in Phone app for more detail.
90     */
91    public static final String EXTRA_CALL_ORIGIN = "com.android.phone.CALL_ORIGIN";
92    public static final String CALL_ORIGIN_DIALTACTS =
93            "com.android.contacts.activities.DialtactsActivity";
94
95    /**
96     * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}.
97     */
98    private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER";
99
100    /** Used both by {@link ActionBar} and {@link ViewPagerAdapter} */
101    private static final int TAB_INDEX_DIALER = 0;
102    private static final int TAB_INDEX_CALL_LOG = 1;
103    private static final int TAB_INDEX_FAVORITES = 2;
104
105    private static final int TAB_INDEX_COUNT = 3;
106
107    private SharedPreferences mPrefs;
108
109    /** Last manually selected tab index */
110    private static final String PREF_LAST_MANUALLY_SELECTED_TAB =
111            "DialtactsActivity_last_manually_selected_tab";
112    private static final int PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT = TAB_INDEX_DIALER;
113
114    private static final int SUBACTIVITY_ACCOUNT_FILTER = 1;
115
116    /**
117     * Listener interface for Fragments accommodated in {@link ViewPager} enabling them to know
118     * when it becomes visible or invisible inside the ViewPager.
119     */
120    public interface ViewPagerVisibilityListener {
121        public void onVisibilityChanged(boolean visible);
122    }
123
124    public class ViewPagerAdapter extends FragmentPagerAdapter {
125        public ViewPagerAdapter(FragmentManager fm) {
126            super(fm);
127        }
128
129        @Override
130        public Fragment getItem(int position) {
131            switch (position) {
132                case TAB_INDEX_DIALER:
133                    return new DialpadFragment();
134                case TAB_INDEX_CALL_LOG:
135                    return new CallLogFragment();
136                case TAB_INDEX_FAVORITES:
137                    return new PhoneFavoriteFragment();
138            }
139            throw new IllegalStateException("No fragment at position " + position);
140        }
141
142        @Override
143        public int getCount() {
144            return TAB_INDEX_COUNT;
145        }
146    }
147
148    /**
149     * True when the app detects user's drag event. This variable should not become true when
150     * mUserTabClick is true.
151     *
152     * During user's drag or tab click, we shouldn't show fake buttons but just show real
153     * ActionBar at the bottom of the screen, for transition animation.
154     */
155    boolean mDuringSwipe = false;
156    /**
157     * True when the app detects user's tab click (at the top of the screen). This variable should
158     * not become true when mDuringSwipe is true.
159     *
160     * During user's drag or tab click, we shouldn't show fake buttons but just show real
161     * ActionBar at the bottom of the screen, for transition animation.
162     */
163    boolean mUserTabClick = false;
164
165    private class PageChangeListener implements OnPageChangeListener {
166        private int mCurrentPosition = -1;
167        /**
168         * Used during page migration, to remember the next position {@link #onPageSelected(int)}
169         * specified.
170         */
171        private int mNextPosition = -1;
172
173        @Override
174        public void onPageScrolled(
175                int position, float positionOffset, int positionOffsetPixels) {
176        }
177
178        @Override
179        public void onPageSelected(int position) {
180            if (DEBUG) Log.d(TAG, "onPageSelected: " + position);
181            final ActionBar actionBar = getActionBar();
182            if (mDialpadFragment != null && !mDuringSwipe) {
183                if (DEBUG) {
184                    Log.d(TAG, "Immediately show/hide fake menu buttons. position: "
185                            + position + ", dragging: " + mDuringSwipe);
186                }
187                mDialpadFragment.updateFakeMenuButtonsVisibility(
188                        position == TAB_INDEX_DIALER && !mDuringSwipe);
189            }
190
191            if (mCurrentPosition == position) {
192                Log.w(TAG, "Previous position and next position became same (" + position + ")");
193            }
194
195            actionBar.selectTab(actionBar.getTabAt(position));
196            mNextPosition = position;
197        }
198
199        public void setCurrentPosition(int position) {
200            mCurrentPosition = position;
201        }
202
203        @Override
204        public void onPageScrollStateChanged(int state) {
205            switch (state) {
206                case ViewPager.SCROLL_STATE_IDLE: {
207                    if (DEBUG) Log.d(TAG, "onPageScrollStateChanged() with SCROLL_STATE_IDLE");
208                    // Interpret IDLE as the end of migration (both swipe and tab click)
209                    mDuringSwipe = false;
210                    mUserTabClick = false;
211
212                    if (mCurrentPosition >= 0) {
213                        sendFragmentVisibilityChange(mCurrentPosition, false);
214                    }
215                    if (mNextPosition >= 0) {
216                        sendFragmentVisibilityChange(mNextPosition, true);
217                    }
218                    invalidateOptionsMenu();
219
220                    mCurrentPosition = mNextPosition;
221                    break;
222                }
223                case ViewPager.SCROLL_STATE_DRAGGING: {
224                    if (DEBUG) Log.d(TAG, "onPageScrollStateChanged() with SCROLL_STATE_DRAGGING");
225                    mDuringSwipe = true;
226                    mUserTabClick = false;
227
228                    if (mCurrentPosition == TAB_INDEX_DIALER) {
229                        sendFragmentVisibilityChange(TAB_INDEX_DIALER, false);
230                        sendFragmentVisibilityChange(TAB_INDEX_CALL_LOG, true);
231                        invalidateOptionsMenu();
232                    }
233                    break;
234                }
235                case ViewPager.SCROLL_STATE_SETTLING: {
236                    if (DEBUG) Log.d(TAG, "onPageScrollStateChanged() with SCROLL_STATE_SETTLING");
237                    mDuringSwipe = true;
238                    mUserTabClick = false;
239                    break;
240                }
241                default:
242                    break;
243            }
244        }
245    }
246
247    private String mFilterText;
248
249    /** Enables horizontal swipe between Fragments. */
250    private ViewPager mViewPager;
251    private final PageChangeListener mPageChangeListener = new PageChangeListener();
252    private DialpadFragment mDialpadFragment;
253    private CallLogFragment mCallLogFragment;
254    private PhoneFavoriteFragment mPhoneFavoriteFragment;
255
256    private final ContactListFilterListener mContactListFilterListener =
257            new ContactListFilterListener() {
258        @Override
259        public void onContactListFilterChanged() {
260            boolean doInvalidateOptionsMenu = false;
261
262            if (mPhoneFavoriteFragment != null && mPhoneFavoriteFragment.isAdded()) {
263                mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
264                doInvalidateOptionsMenu = true;
265            }
266
267            if (mSearchFragment != null && mSearchFragment.isAdded()) {
268                mSearchFragment.setFilter(mContactListFilterController.getFilter());
269                doInvalidateOptionsMenu = true;
270            } else {
271                Log.w(TAG, "Search Fragment isn't available when ContactListFilter is changed");
272            }
273
274            if (doInvalidateOptionsMenu) {
275                invalidateOptionsMenu();
276            }
277        }
278    };
279
280    private final TabListener mTabListener = new TabListener() {
281        @Override
282        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
283            if (DEBUG) Log.d(TAG, "onTabUnselected(). tab: " + tab);
284        }
285
286        @Override
287        public void onTabSelected(Tab tab, FragmentTransaction ft) {
288            if (DEBUG) {
289                Log.d(TAG, "onTabSelected(). tab: " + tab + ", mDuringSwipe: " + mDuringSwipe);
290            }
291            // When the user swipes the screen horizontally, this method will be called after
292            // ViewPager.SCROLL_STATE_DRAGGING and ViewPager.SCROLL_STATE_SETTLING events, while
293            // when the user clicks a tab at the ActionBar at the top, this will be called before
294            // them. This logic interprets the order difference as a difference of the user action.
295            if (!mDuringSwipe) {
296                if (mDialpadFragment != null) {
297                    if (DEBUG) Log.d(TAG, "Immediately hide fake buttons for tab selection case");
298                    mDialpadFragment.updateFakeMenuButtonsVisibility(false);
299                }
300                mUserTabClick = true;
301            }
302
303            if (mViewPager.getCurrentItem() != tab.getPosition()) {
304                mViewPager.setCurrentItem(tab.getPosition(), true);
305            }
306
307            // During the call, we don't remember the tab position.
308            if (!DialpadFragment.phoneIsInUse()) {
309                // Remember this tab index. This function is also called, if the tab is set
310                // automatically in which case the setter (setCurrentTab) has to set this to its old
311                // value afterwards
312                mLastManuallySelectedFragment = tab.getPosition();
313            }
314        }
315
316        @Override
317        public void onTabReselected(Tab tab, FragmentTransaction ft) {
318            if (DEBUG) Log.d(TAG, "onTabReselected");
319        }
320    };
321
322    /**
323     * Fragment for searching phone numbers. Unlike the other Fragments, this doesn't correspond
324     * to tab but is shown by a search action.
325     */
326    private PhoneNumberPickerFragment mSearchFragment;
327    /**
328     * True when this Activity is in its search UI (with a {@link SearchView} and
329     * {@link PhoneNumberPickerFragment}).
330     */
331    private boolean mInSearchUi;
332    private SearchView mSearchView;
333
334    private final OnClickListener mFilterOptionClickListener = new OnClickListener() {
335        @Override
336        public void onClick(View view) {
337            final PopupMenu popupMenu = new PopupMenu(DialtactsActivity.this, view);
338            final Menu menu = popupMenu.getMenu();
339            popupMenu.inflate(R.menu.dialtacts_search_options);
340            final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
341            filterOptionMenuItem.setOnMenuItemClickListener(mFilterOptionsMenuItemClickListener);
342            final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
343            addContactOptionMenuItem.setIntent(
344                    new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
345            popupMenu.show();
346        }
347    };
348
349    /**
350     * The index of the Fragment (or, the tab) that has last been manually selected.
351     * This value does not keep track of programmatically set Tabs (e.g. Call Log after a Call)
352     */
353    private int mLastManuallySelectedFragment;
354
355    private ContactListFilterController mContactListFilterController;
356    private OnMenuItemClickListener mFilterOptionsMenuItemClickListener =
357            new OnMenuItemClickListener() {
358        @Override
359        public boolean onMenuItemClick(MenuItem item) {
360            AccountFilterUtil.startAccountFilterActivityForResult(
361                    DialtactsActivity.this, SUBACTIVITY_ACCOUNT_FILTER);
362            return true;
363        }
364    };
365
366    private OnMenuItemClickListener mSearchMenuItemClickListener =
367            new OnMenuItemClickListener() {
368        @Override
369        public boolean onMenuItemClick(MenuItem item) {
370            enterSearchUi();
371            return true;
372        }
373    };
374
375    /**
376     * Listener used when one of phone numbers in search UI is selected. This will initiate a
377     * phone call using the phone number.
378     */
379    private final OnPhoneNumberPickerActionListener mPhoneNumberPickerActionListener =
380            new OnPhoneNumberPickerActionListener() {
381                @Override
382                public void onPickPhoneNumberAction(Uri dataUri) {
383                    // Specify call-origin so that users will see the previous tab instead of
384                    // CallLog screen (search UI will be automatically exited).
385                    PhoneNumberInteraction.startInteractionForPhoneCall(
386                            DialtactsActivity.this, dataUri,
387                            CALL_ORIGIN_DIALTACTS);
388                }
389
390                @Override
391                public void onShortcutIntentCreated(Intent intent) {
392                    Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring.");
393                }
394
395                @Override
396                public void onHomeInActionBarSelected() {
397                    exitSearchUi();
398                }
399    };
400
401    /**
402     * Listener used to send search queries to the phone search fragment.
403     */
404    private final OnQueryTextListener mPhoneSearchQueryTextListener =
405            new OnQueryTextListener() {
406                @Override
407                public boolean onQueryTextSubmit(String query) {
408                    View view = getCurrentFocus();
409                    if (view != null) {
410                        hideInputMethod(view);
411                        view.clearFocus();
412                    }
413                    return true;
414                }
415
416                @Override
417                public boolean onQueryTextChange(String newText) {
418                    // Show search result with non-empty text. Show a bare list otherwise.
419                    if (mSearchFragment != null) {
420                        mSearchFragment.setQueryString(newText, true);
421                    }
422                    return true;
423                }
424    };
425
426    /**
427     * Listener used to handle the "close" button on the right side of {@link SearchView}.
428     * If some text is in the search view, this will clean it up. Otherwise this will exit
429     * the search UI and let users go back to usual Phone UI.
430     *
431     * This does _not_ handle back button.
432     */
433    private final OnCloseListener mPhoneSearchCloseListener =
434            new OnCloseListener() {
435                @Override
436                public boolean onClose() {
437                    if (!TextUtils.isEmpty(mSearchView.getQuery())) {
438                        mSearchView.setQuery(null, true);
439                    }
440                    return true;
441                }
442    };
443
444    private final View.OnLayoutChangeListener mFirstLayoutListener
445            = new View.OnLayoutChangeListener() {
446        @Override
447        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
448                int oldTop, int oldRight, int oldBottom) {
449            v.removeOnLayoutChangeListener(this); // Unregister self.
450            addSearchFragment();
451        }
452    };
453
454    @Override
455    protected void onCreate(Bundle icicle) {
456        super.onCreate(icicle);
457
458        final Intent intent = getIntent();
459        fixIntent(intent);
460
461        setContentView(R.layout.dialtacts_activity);
462
463        mContactListFilterController = ContactListFilterController.getInstance(this);
464        mContactListFilterController.addListener(mContactListFilterListener);
465
466        findViewById(R.id.dialtacts_frame).addOnLayoutChangeListener(mFirstLayoutListener);
467
468        mViewPager = (ViewPager) findViewById(R.id.pager);
469        mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
470        mViewPager.setOnPageChangeListener(mPageChangeListener);
471
472        // Setup the ActionBar tabs (the order matches the tab-index contants TAB_INDEX_*)
473        setupDialer();
474        setupCallLog();
475        setupFavorites();
476        getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
477        getActionBar().setDisplayShowTitleEnabled(false);
478        getActionBar().setDisplayShowHomeEnabled(false);
479
480        // Load the last manually loaded tab
481        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
482        mLastManuallySelectedFragment = mPrefs.getInt(PREF_LAST_MANUALLY_SELECTED_TAB,
483                PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT);
484        if (mLastManuallySelectedFragment >= TAB_INDEX_COUNT) {
485            // Stored value may have exceeded the number of current tabs. Reset it.
486            mLastManuallySelectedFragment = PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT;
487        }
488
489        setCurrentTab(intent);
490
491        if (UI.FILTER_CONTACTS_ACTION.equals(intent.getAction())
492                && icicle == null) {
493            setupFilterText(intent);
494        }
495    }
496
497    @Override
498    public void onStart() {
499        super.onStart();
500        if (mPhoneFavoriteFragment != null) {
501            mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
502        }
503        if (mSearchFragment != null) {
504            mSearchFragment.setFilter(mContactListFilterController.getFilter());
505        }
506
507        if (mDuringSwipe || mUserTabClick) {
508            if (DEBUG) Log.d(TAG, "reset buggy flag state..");
509            mDuringSwipe = false;
510            mUserTabClick = false;
511        }
512    }
513
514    @Override
515    public void onDestroy() {
516        super.onDestroy();
517        mContactListFilterController.removeListener(mContactListFilterListener);
518    }
519
520    /**
521     * Add search fragment.  Note this is called during onLayout, so there's some restrictions,
522     * such as executePendingTransaction can't be used in it.
523     */
524    private void addSearchFragment() {
525        // In order to take full advantage of "fragment deferred start", we need to create the
526        // search fragment after all other fragments are created.
527        // The other fragments are created by the ViewPager on the first onMeasure().
528        // We use the first onLayout call, which is after onMeasure().
529
530        // Just return if the fragment is already created, which happens after configuration
531        // changes.
532        if (mSearchFragment != null) return;
533
534        final FragmentTransaction ft = getFragmentManager().beginTransaction();
535        final Fragment searchFragment = new PhoneNumberPickerFragment();
536
537        searchFragment.setUserVisibleHint(false);
538        ft.add(R.id.dialtacts_frame, searchFragment);
539        ft.hide(searchFragment);
540        ft.commitAllowingStateLoss();
541    }
542
543    private void prepareSearchView() {
544        final View searchViewLayout =
545                getLayoutInflater().inflate(R.layout.dialtacts_custom_action_bar, null);
546        mSearchView = (SearchView) searchViewLayout.findViewById(R.id.search_view);
547        mSearchView.setOnQueryTextListener(mPhoneSearchQueryTextListener);
548        mSearchView.setOnCloseListener(mPhoneSearchCloseListener);
549        // Since we're using a custom layout for showing SearchView instead of letting the
550        // search menu icon do that job, we need to manually configure the View so it looks
551        // "shown via search menu".
552        // - it should be iconified by default
553        // - it should not be iconified at this time
554        // See also comments for onActionViewExpanded()/onActionViewCollapsed()
555        mSearchView.setIconifiedByDefault(true);
556        mSearchView.setQueryHint(getString(R.string.hint_findContacts));
557        mSearchView.setIconified(false);
558        mSearchView.setOnQueryTextFocusChangeListener(new OnFocusChangeListener() {
559            @Override
560            public void onFocusChange(View view, boolean hasFocus) {
561                if (hasFocus) {
562                    showInputMethod(view.findFocus());
563                }
564            }
565        });
566
567        if (!ViewConfiguration.get(this).hasPermanentMenuKey()) {
568            // Filter option menu should be shown on the right side of SearchView.
569            final View filterOptionView = searchViewLayout.findViewById(R.id.search_option);
570            filterOptionView.setVisibility(View.VISIBLE);
571            filterOptionView.setOnClickListener(mFilterOptionClickListener);
572        }
573
574        getActionBar().setCustomView(searchViewLayout,
575                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
576    }
577
578    @Override
579    public void onAttachFragment(Fragment fragment) {
580        // This method can be called before onCreate(), at which point we cannot rely on ViewPager.
581        // In that case, we will setup the "current position" soon after the ViewPager is ready.
582        final int currentPosition = mViewPager != null ? mViewPager.getCurrentItem() : -1;
583
584        if (fragment instanceof DialpadFragment) {
585            mDialpadFragment = (DialpadFragment) fragment;
586            mDialpadFragment.setListener(mDialpadListener);
587            if (currentPosition == TAB_INDEX_DIALER) {
588                mDialpadFragment.onVisibilityChanged(true);
589            }
590        } else if (fragment instanceof CallLogFragment) {
591            mCallLogFragment = (CallLogFragment) fragment;
592            if (currentPosition == TAB_INDEX_CALL_LOG) {
593                mCallLogFragment.onVisibilityChanged(true);
594            }
595        } else if (fragment instanceof PhoneFavoriteFragment) {
596            mPhoneFavoriteFragment = (PhoneFavoriteFragment) fragment;
597            mPhoneFavoriteFragment.setListener(mPhoneFavoriteListener);
598            if (mContactListFilterController != null
599                    && mContactListFilterController.getFilter() != null) {
600                mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
601            }
602        } else if (fragment instanceof PhoneNumberPickerFragment) {
603            mSearchFragment = (PhoneNumberPickerFragment) fragment;
604            mSearchFragment.setOnPhoneNumberPickerActionListener(mPhoneNumberPickerActionListener);
605            mSearchFragment.setQuickContactEnabled(true);
606            mSearchFragment.setDarkTheme(true);
607            mSearchFragment.setPhotoPosition(ContactListItemView.PhotoPosition.LEFT);
608            if (mContactListFilterController != null
609                    && mContactListFilterController.getFilter() != null) {
610                mSearchFragment.setFilter(mContactListFilterController.getFilter());
611            }
612            // Here we assume that we're not on the search mode, so let's hide the fragment.
613            //
614            // We get here either when the fragment is created (normal case), or after configuration
615            // changes.  In the former case, we're not in search mode because we can only
616            // enter search mode if the fragment is created.  (see enterSearchUi())
617            // In the latter case we're not in search mode either because we don't retain
618            // mInSearchUi -- ideally we should but at this point it's not supported.
619            mSearchFragment.setUserVisibleHint(false);
620            // After configuration changes fragments will forget their "hidden" state, so make
621            // sure to hide it.
622            if (!mSearchFragment.isHidden()) {
623                final FragmentTransaction transaction = getFragmentManager().beginTransaction();
624                transaction.hide(mSearchFragment);
625                transaction.commitAllowingStateLoss();
626            }
627        }
628    }
629
630    @Override
631    protected void onPause() {
632        super.onPause();
633
634        mPrefs.edit().putInt(PREF_LAST_MANUALLY_SELECTED_TAB, mLastManuallySelectedFragment)
635                .apply();
636    }
637
638    private void fixIntent(Intent intent) {
639        // This should be cleaned up: the call key used to send an Intent
640        // that just said to go to the recent calls list.  It now sends this
641        // abstract action, but this class hasn't been rewritten to deal with it.
642        if (Intent.ACTION_CALL_BUTTON.equals(intent.getAction())) {
643            intent.setDataAndType(Calls.CONTENT_URI, Calls.CONTENT_TYPE);
644            intent.putExtra("call_key", true);
645            setIntent(intent);
646        }
647    }
648
649    private void setupDialer() {
650        final Tab tab = getActionBar().newTab();
651        tab.setContentDescription(R.string.dialerIconLabel);
652        tab.setTabListener(mTabListener);
653        tab.setIcon(R.drawable.ic_tab_dialer);
654        getActionBar().addTab(tab);
655    }
656
657    private void setupCallLog() {
658        final Tab tab = getActionBar().newTab();
659        tab.setContentDescription(R.string.recentCallsIconLabel);
660        tab.setIcon(R.drawable.ic_tab_recent);
661        tab.setTabListener(mTabListener);
662        getActionBar().addTab(tab);
663    }
664
665    private void setupFavorites() {
666        final Tab tab = getActionBar().newTab();
667        tab.setContentDescription(R.string.contactsFavoritesLabel);
668        tab.setIcon(R.drawable.ic_tab_all);
669        tab.setTabListener(mTabListener);
670        getActionBar().addTab(tab);
671    }
672
673    /**
674     * Returns true if the intent is due to hitting the green send key while in a call.
675     *
676     * @param intent the intent that launched this activity
677     * @param recentCallsRequest true if the intent is requesting to view recent calls
678     * @return true if the intent is due to hitting the green send key while in a call
679     */
680    private boolean isSendKeyWhileInCall(final Intent intent,
681            final boolean recentCallsRequest) {
682        // If there is a call in progress go to the call screen
683        if (recentCallsRequest) {
684            final boolean callKey = intent.getBooleanExtra("call_key", false);
685
686            try {
687                ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
688                if (callKey && phone != null && phone.showCallScreen()) {
689                    return true;
690                }
691            } catch (RemoteException e) {
692                Log.e(TAG, "Failed to handle send while in call", e);
693            }
694        }
695
696        return false;
697    }
698
699    /**
700     * Sets the current tab based on the intent's request type
701     *
702     * @param intent Intent that contains information about which tab should be selected
703     */
704    private void setCurrentTab(Intent intent) {
705        // If we got here by hitting send and we're in call forward along to the in-call activity
706        final boolean recentCallsRequest = Calls.CONTENT_TYPE.equals(intent.getType());
707        if (isSendKeyWhileInCall(intent, recentCallsRequest)) {
708            finish();
709            return;
710        }
711
712        // Remember the old manually selected tab index so that it can be restored if it is
713        // overwritten by one of the programmatic tab selections
714        final int savedTabIndex = mLastManuallySelectedFragment;
715
716        final int tabIndex;
717        if (DialpadFragment.phoneIsInUse() || isDialIntent(intent)) {
718            tabIndex = TAB_INDEX_DIALER;
719        } else if (recentCallsRequest) {
720            tabIndex = TAB_INDEX_CALL_LOG;
721        } else {
722            tabIndex = mLastManuallySelectedFragment;
723        }
724
725        final int previousItemIndex = mViewPager.getCurrentItem();
726        mViewPager.setCurrentItem(tabIndex, false /* smoothScroll */);
727        if (previousItemIndex != tabIndex) {
728            sendFragmentVisibilityChange(previousItemIndex, false);
729        }
730        mPageChangeListener.setCurrentPosition(tabIndex);
731        sendFragmentVisibilityChange(tabIndex, true);
732
733        // Restore to the previous manual selection
734        mLastManuallySelectedFragment = savedTabIndex;
735        mDuringSwipe = false;
736        mUserTabClick = false;
737    }
738
739    @Override
740    public void onNewIntent(Intent newIntent) {
741        setIntent(newIntent);
742        fixIntent(newIntent);
743        setCurrentTab(newIntent);
744        final String action = newIntent.getAction();
745        if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
746            setupFilterText(newIntent);
747        }
748        if (mInSearchUi || (mSearchFragment != null && mSearchFragment.isVisible())) {
749            exitSearchUi();
750        }
751
752        if (mViewPager.getCurrentItem() == TAB_INDEX_DIALER) {
753            if (mDialpadFragment != null) {
754                mDialpadFragment.configureScreenFromIntent(newIntent);
755            } else {
756                Log.e(TAG, "DialpadFragment isn't ready yet when the tab is already selected.");
757            }
758        }
759        invalidateOptionsMenu();
760    }
761
762    /** Returns true if the given intent contains a phone number to populate the dialer with */
763    private boolean isDialIntent(Intent intent) {
764        final String action = intent.getAction();
765        if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {
766            return true;
767        }
768        if (Intent.ACTION_VIEW.equals(action)) {
769            final Uri data = intent.getData();
770            if (data != null && "tel".equals(data.getScheme())) {
771                return true;
772            }
773        }
774        return false;
775    }
776
777    /**
778     * Retrieves the filter text stored in {@link #setupFilterText(Intent)}.
779     * This text originally came from a FILTER_CONTACTS_ACTION intent received
780     * by this activity. The stored text will then be cleared after after this
781     * method returns.
782     *
783     * @return The stored filter text
784     */
785    public String getAndClearFilterText() {
786        String filterText = mFilterText;
787        mFilterText = null;
788        return filterText;
789    }
790
791    /**
792     * Stores the filter text associated with a FILTER_CONTACTS_ACTION intent.
793     * This is so child activities can check if they are supposed to display a filter.
794     *
795     * @param intent The intent received in {@link #onNewIntent(Intent)}
796     */
797    private void setupFilterText(Intent intent) {
798        // If the intent was relaunched from history, don't apply the filter text.
799        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
800            return;
801        }
802        String filter = intent.getStringExtra(UI.FILTER_TEXT_EXTRA_KEY);
803        if (filter != null && filter.length() > 0) {
804            mFilterText = filter;
805        }
806    }
807
808    @Override
809    public void onBackPressed() {
810        if (mInSearchUi) {
811            // We should let the user go back to usual screens with tabs.
812            exitSearchUi();
813        } else if (isTaskRoot()) {
814            // Instead of stopping, simply push this to the back of the stack.
815            // This is only done when running at the top of the stack;
816            // otherwise, we have been launched by someone else so need to
817            // allow the user to go back to the caller.
818            moveTaskToBack(false);
819        } else {
820            super.onBackPressed();
821        }
822    }
823
824    private DialpadFragment.Listener mDialpadListener = new DialpadFragment.Listener() {
825        @Override
826        public void onSearchButtonPressed() {
827            enterSearchUi();
828        }
829    };
830
831    private PhoneFavoriteFragment.Listener mPhoneFavoriteListener =
832            new PhoneFavoriteFragment.Listener() {
833        @Override
834        public void onContactSelected(Uri contactUri) {
835            PhoneNumberInteraction.startInteractionForPhoneCall(
836                    DialtactsActivity.this, contactUri,
837                    CALL_ORIGIN_DIALTACTS);
838        }
839    };
840
841    @Override
842    public boolean onCreateOptionsMenu(Menu menu) {
843        MenuInflater inflater = getMenuInflater();
844        inflater.inflate(R.menu.dialtacts_options, menu);
845        return true;
846    }
847
848    @Override
849    public boolean onPrepareOptionsMenu(Menu menu) {
850        final MenuItem searchMenuItem = menu.findItem(R.id.search_on_action_bar);
851        final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
852        final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
853        final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings);
854        final MenuItem fakeMenuItem = menu.findItem(R.id.fake_menu_item);
855        Tab tab = getActionBar().getSelectedTab();
856        if (mInSearchUi) {
857            searchMenuItem.setVisible(false);
858            if (ViewConfiguration.get(this).hasPermanentMenuKey()) {
859                filterOptionMenuItem.setVisible(true);
860                filterOptionMenuItem.setOnMenuItemClickListener(
861                        mFilterOptionsMenuItemClickListener);
862                addContactOptionMenuItem.setVisible(true);
863                addContactOptionMenuItem.setIntent(
864                        new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
865            } else {
866                // Filter option menu should be not be shown as a overflow menu.
867                filterOptionMenuItem.setVisible(false);
868                addContactOptionMenuItem.setVisible(false);
869            }
870            callSettingsMenuItem.setVisible(false);
871            fakeMenuItem.setVisible(false);
872        } else {
873            final boolean showCallSettingsMenu;
874            if (tab != null && tab.getPosition() == TAB_INDEX_DIALER) {
875                if (DEBUG) {
876                    Log.d(TAG, "onPrepareOptionsMenu(dialer). swipe: " + mDuringSwipe
877                            + ", user tab click: " + mUserTabClick);
878                }
879                if (mDuringSwipe || mUserTabClick) {
880                    // During horizontal movement, we just show real ActionBar menu items.
881                    searchMenuItem.setVisible(true);
882                    searchMenuItem.setOnMenuItemClickListener(mSearchMenuItemClickListener);
883                    showCallSettingsMenu = true;
884
885                    fakeMenuItem.setVisible(ViewConfiguration.get(this).hasPermanentMenuKey());
886                } else {
887                    searchMenuItem.setVisible(false);
888                    // When permanent menu key is _not_ available, the call settings menu should be
889                    // available via DialpadFragment.
890                    showCallSettingsMenu = ViewConfiguration.get(this).hasPermanentMenuKey();
891                    fakeMenuItem.setVisible(false);
892                }
893            } else {
894                searchMenuItem.setVisible(true);
895                searchMenuItem.setOnMenuItemClickListener(mSearchMenuItemClickListener);
896                showCallSettingsMenu = true;
897                fakeMenuItem.setVisible(ViewConfiguration.get(this).hasPermanentMenuKey());
898            }
899            if (tab != null && tab.getPosition() == TAB_INDEX_FAVORITES) {
900                filterOptionMenuItem.setVisible(true);
901                filterOptionMenuItem.setOnMenuItemClickListener(
902                        mFilterOptionsMenuItemClickListener);
903                addContactOptionMenuItem.setVisible(true);
904                addContactOptionMenuItem.setIntent(
905                        new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
906            } else {
907                filterOptionMenuItem.setVisible(false);
908                addContactOptionMenuItem.setVisible(false);
909            }
910
911            if (showCallSettingsMenu) {
912                callSettingsMenuItem.setVisible(true);
913                callSettingsMenuItem.setIntent(DialtactsActivity.getCallSettingsIntent());
914            } else {
915                callSettingsMenuItem.setVisible(false);
916            }
917        }
918
919        return true;
920    }
921
922    @Override
923    public void startSearch(String initialQuery, boolean selectInitialQuery,
924            Bundle appSearchData, boolean globalSearch) {
925        if (mSearchFragment != null && mSearchFragment.isAdded() && !globalSearch) {
926            if (mInSearchUi) {
927                if (mSearchView.hasFocus()) {
928                    showInputMethod(mSearchView.findFocus());
929                } else {
930                    mSearchView.requestFocus();
931                }
932            } else {
933                enterSearchUi();
934            }
935        } else {
936            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
937        }
938    }
939
940    /**
941     * Hides every tab and shows search UI for phone lookup.
942     */
943    private void enterSearchUi() {
944        if (mSearchFragment == null) {
945            // We add the search fragment dynamically in the first onLayoutChange() and
946            // mSearchFragment is set sometime later when the fragment transaction is actually
947            // executed, which means there's a window when users are able to hit the (physical)
948            // search key but mSearchFragment is still null.
949            // It's quite hard to handle this case right, so let's just ignore the search key
950            // in this case.  Users can just hit it again and it will work this time.
951            return;
952        }
953        if (mSearchView == null) {
954            prepareSearchView();
955        }
956
957        final ActionBar actionBar = getActionBar();
958
959        final Tab tab = actionBar.getSelectedTab();
960
961        // User can search during the call, but we don't want to remember the status.
962        if (tab != null && !DialpadFragment.phoneIsInUse()) {
963            mLastManuallySelectedFragment = tab.getPosition();
964        }
965
966        mSearchView.setQuery(null, true);
967
968        actionBar.setDisplayShowCustomEnabled(true);
969        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
970        actionBar.setDisplayShowHomeEnabled(true);
971        actionBar.setDisplayHomeAsUpEnabled(true);
972
973        sendFragmentVisibilityChange(mViewPager.getCurrentItem(), false);
974
975        // Show the search fragment and hide everything else.
976        mSearchFragment.setUserVisibleHint(true);
977        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
978        transaction.show(mSearchFragment);
979        transaction.commitAllowingStateLoss();
980        mViewPager.setVisibility(View.GONE);
981
982        // We need to call this and onActionViewCollapsed() manually, since we are using a custom
983        // layout instead of asking the search menu item to take care of SearchView.
984        mSearchView.onActionViewExpanded();
985        mInSearchUi = true;
986    }
987
988    private void showInputMethod(View view) {
989        InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
990        if (imm != null) {
991            if (!imm.showSoftInput(view, 0)) {
992                Log.w(TAG, "Failed to show soft input method.");
993            }
994        }
995    }
996
997    private void hideInputMethod(View view) {
998        InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
999        if (imm != null && view != null) {
1000            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
1001        }
1002    }
1003
1004    /**
1005     * Goes back to usual Phone UI with tags. Previously selected Tag and associated Fragment
1006     * should be automatically focused again.
1007     */
1008    private void exitSearchUi() {
1009        final ActionBar actionBar = getActionBar();
1010
1011        // Hide the search fragment, if exists.
1012        if (mSearchFragment != null) {
1013            mSearchFragment.setUserVisibleHint(false);
1014
1015            final FragmentTransaction transaction = getFragmentManager().beginTransaction();
1016            transaction.hide(mSearchFragment);
1017            transaction.commitAllowingStateLoss();
1018        }
1019
1020        // We want to hide SearchView and show Tabs. Also focus on previously selected one.
1021        actionBar.setDisplayShowCustomEnabled(false);
1022        actionBar.setDisplayShowHomeEnabled(false);
1023        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
1024
1025        sendFragmentVisibilityChange(mViewPager.getCurrentItem(), true);
1026
1027        // Before exiting the search screen, reset swipe state.
1028        mDuringSwipe = false;
1029        mUserTabClick = false;
1030
1031        mViewPager.setVisibility(View.VISIBLE);
1032
1033        hideInputMethod(getCurrentFocus());
1034
1035        // Request to update option menu.
1036        invalidateOptionsMenu();
1037
1038        // See comments in onActionViewExpanded()
1039        mSearchView.onActionViewCollapsed();
1040        mInSearchUi = false;
1041    }
1042
1043    private Fragment getFragmentAt(int position) {
1044        switch (position) {
1045            case TAB_INDEX_DIALER:
1046                return mDialpadFragment;
1047            case TAB_INDEX_CALL_LOG:
1048                return mCallLogFragment;
1049            case TAB_INDEX_FAVORITES:
1050                return mPhoneFavoriteFragment;
1051            default:
1052                throw new IllegalStateException("Unknown fragment index: " + position);
1053        }
1054    }
1055
1056    private void sendFragmentVisibilityChange(int position, boolean visibility) {
1057        final Fragment fragment = getFragmentAt(position);
1058        if (fragment instanceof ViewPagerVisibilityListener) {
1059            ((ViewPagerVisibilityListener) fragment).onVisibilityChanged(visibility);
1060        }
1061    }
1062
1063    /** Returns an Intent to launch Call Settings screen */
1064    public static Intent getCallSettingsIntent() {
1065        final Intent intent = new Intent(Intent.ACTION_MAIN);
1066        intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME);
1067        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
1068        return intent;
1069    }
1070
1071    @Override
1072    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1073        if (resultCode != Activity.RESULT_OK) {
1074            return;
1075        }
1076        switch (requestCode) {
1077            case SUBACTIVITY_ACCOUNT_FILTER: {
1078                AccountFilterUtil.handleAccountFilterResult(
1079                        mContactListFilterController, resultCode, data);
1080            }
1081            break;
1082        }
1083    }
1084}
1085