Phone: Delay-create search fragment

In order to take full advantage of the new "fragment deferred start" feature.
Now we create the search fragment in the first onLayout call, which is after
all other fragments are created.

Bug 5550646

Change-Id: I2e3710fb4ea6ff4301309538dd58bdef5811bc8b
diff --git a/res/layout/dialtacts_activity.xml b/res/layout/dialtacts_activity.xml
index af85bba..d1af632 100644
--- a/res/layout/dialtacts_activity.xml
+++ b/res/layout/dialtacts_activity.xml
@@ -17,18 +17,11 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:layout_marginTop="?android:attr/actionBarSize">
-
+    android:layout_marginTop="?android:attr/actionBarSize"
+    android:id="@+id/dialtacts_frame"
+    >
     <com.android.contacts.activities.DialtactsViewPager
         android:id="@+id/pager"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
-
-    <!-- For phone search UI -->
-    <fragment
-        android:id="@+id/phone_number_picker_fragment"
-        class="com.android.contacts.list.PhoneNumberPickerFragment"
-        android:layout_height="match_parent"
-        android:layout_width="match_parent" />
-
 </FrameLayout>
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 2268743..7e95fed 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -120,10 +120,6 @@
     }
 
     public class ViewPagerAdapter extends FragmentPagerAdapter {
-        private DialpadFragment mDialpadFragment;
-        private CallLogFragment mCallLogFragment;
-        private PhoneFavoriteFragment mPhoneFavoriteFragment;
-
         public ViewPagerAdapter(FragmentManager fm) {
             super(fm);
         }
@@ -132,20 +128,11 @@
         public Fragment getItem(int position) {
             switch (position) {
                 case TAB_INDEX_DIALER:
-                    if (mDialpadFragment == null) {
-                        mDialpadFragment = new DialpadFragment();
-                    }
-                    return mDialpadFragment;
+                    return new DialpadFragment();
                 case TAB_INDEX_CALL_LOG:
-                    if (mCallLogFragment == null) {
-                        mCallLogFragment = new CallLogFragment();
-                    }
-                    return mCallLogFragment;
+                    return new CallLogFragment();
                 case TAB_INDEX_FAVORITES:
-                    if (mPhoneFavoriteFragment == null) {
-                        mPhoneFavoriteFragment = new PhoneFavoriteFragment();
-                    }
-                    return mPhoneFavoriteFragment;
+                    return new PhoneFavoriteFragment();
             }
             throw new IllegalStateException("No fragment at position " + position);
         }
@@ -362,7 +349,9 @@
                 @Override
                 public boolean onQueryTextChange(String newText) {
                     // Show search result with non-empty text. Show a bare list otherwise.
-                    mSearchFragment.setQueryString(newText, true);
+                    if (mSearchFragment != null) {
+                        mSearchFragment.setQueryString(newText, true);
+                    }
                     return true;
                 }
     };
@@ -385,6 +374,16 @@
                 }
     };
 
+    private final View.OnLayoutChangeListener mFirstLayoutListener
+            = new View.OnLayoutChangeListener() {
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+                int oldTop, int oldRight, int oldBottom) {
+            v.removeOnLayoutChangeListener(this); // Unregister self.
+            addSearchFragment();
+        }
+    };
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -397,6 +396,8 @@
         mContactListFilterController = ContactListFilterController.getInstance(this);
         mContactListFilterController.addListener(mContactListFilterListener);
 
+        findViewById(R.id.dialtacts_frame).addOnLayoutChangeListener(mFirstLayoutListener);
+
         mViewPager = (ViewPager) findViewById(R.id.pager);
         mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
         mViewPager.setOnPageChangeListener(mPageChangeListener);
@@ -443,6 +444,29 @@
         mContactListFilterController.removeListener(mContactListFilterListener);
     }
 
+    /**
+     * Add search fragment.  Note this is called during onLayout, so there's some restrictions,
+     * such as executePendingTransaction can't be used in it.
+     */
+    private void addSearchFragment() {
+        // In order to take full advantage of "fragment deferred start", we need to create the
+        // search fragment after all other fragments are created.
+        // The other fragments are created by the ViewPager on the first onMeasure().
+        // We use the first onLayout call, which is after onMeasure().
+
+        // Just return if the fragment is already created, which happens after configuration
+        // changes.
+        if (mSearchFragment != null) return;
+
+        final FragmentTransaction ft = getFragmentManager().beginTransaction();
+        final Fragment searchFragment = new PhoneNumberPickerFragment();
+
+        searchFragment.setUserVisibleHint(false);
+        ft.add(R.id.dialtacts_frame, searchFragment);
+        ft.hide(searchFragment);
+        ft.commitAllowingStateLoss();
+    }
+
     private void prepareSearchView() {
         final View searchViewLayout =
                 getLayoutInflater().inflate(R.layout.dialtacts_custom_action_bar, null);
@@ -508,14 +532,25 @@
             mSearchFragment.setQuickContactEnabled(true);
             mSearchFragment.setDarkTheme(true);
             mSearchFragment.setPhotoPosition(ContactListItemView.PhotoPosition.LEFT);
-            mSearchFragment.setUserVisibleHint(false);
-            final FragmentTransaction transaction = getFragmentManager().beginTransaction();
-            if (mInSearchUi) {
-                transaction.show(mSearchFragment);
-            } else {
-                transaction.hide(mSearchFragment);
+            if (mContactListFilterController != null
+                    && mContactListFilterController.getFilter() != null) {
+                mSearchFragment.setFilter(mContactListFilterController.getFilter());
             }
-            transaction.commitAllowingStateLoss();
+            // Here we assume that we're not on the search mode, so let's hide the fragment.
+            //
+            // We get here either when the fragment is created (normal case), or after configuration
+            // changes.  In the former case, we're not in search mode because we can only
+            // enter search mode if the fragment is created.  (see enterSearchUi())
+            // In the latter case we're not in search mode either because we don't retain
+            // mInSearchUi -- ideally we should but at this point it's not supported.
+            mSearchFragment.setUserVisibleHint(false);
+            // After configuration changes fragments will forget their "hidden" state, so make
+            // sure to hide it.
+            if (!mSearchFragment.isHidden()) {
+                final FragmentTransaction transaction = getFragmentManager().beginTransaction();
+                transaction.hide(mSearchFragment);
+                transaction.commitAllowingStateLoss();
+            }
         }
     }
 
@@ -635,7 +670,7 @@
         if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
             setupFilterText(newIntent);
         }
-        if (mInSearchUi || mSearchFragment.isVisible()) {
+        if (mInSearchUi || (mSearchFragment != null && mSearchFragment.isVisible())) {
             exitSearchUi();
         }
 
@@ -814,6 +849,15 @@
      * Hides every tab and shows search UI for phone lookup.
      */
     private void enterSearchUi() {
+        if (mSearchFragment == null) {
+            // We add the search fragment dynamically in the first onLayoutChange() and
+            // mSearchFragment is set sometime later when the fragment transaction is actually
+            // executed, which means there's a window when users are able to hit the (physical)
+            // search key but mSearchFragment is still null.
+            // It's quite hard to handle this case right, so let's just ignore the search key
+            // in this case.  Users can just hit it again and it will work this time.
+            return;
+        }
         if (mSearchView == null) {
             prepareSearchView();
         }
@@ -837,6 +881,7 @@
         sendFragmentVisibilityChange(mViewPager.getCurrentItem(), false);
 
         // Show the search fragment and hide everything else.
+        mSearchFragment.setUserVisibleHint(true);
         final FragmentTransaction transaction = getFragmentManager().beginTransaction();
         transaction.show(mSearchFragment);
         transaction.commitAllowingStateLoss();
@@ -846,7 +891,6 @@
         // layout instead of asking the search menu item to take care of SearchView.
         mSearchView.onActionViewExpanded();
         mInSearchUi = true;
-        mSearchFragment.setUserVisibleHint(true);
     }
 
     private void showInputMethod(View view) {
@@ -872,9 +916,14 @@
     private void exitSearchUi() {
         final ActionBar actionBar = getActionBar();
 
-        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
-        transaction.hide(mSearchFragment);
-        transaction.commitAllowingStateLoss();
+        // Hide the search fragment, if exists.
+        if (mSearchFragment != null) {
+            mSearchFragment.setUserVisibleHint(false);
+
+            final FragmentTransaction transaction = getFragmentManager().beginTransaction();
+            transaction.hide(mSearchFragment);
+            transaction.commitAllowingStateLoss();
+        }
 
         // We want to hide SearchView and show Tabs. Also focus on previously selected one.
         actionBar.setDisplayShowCustomEnabled(false);