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