Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit db0d8669 authored by Katherine Kuan's avatar Katherine Kuan
Browse files

Reuse fragments when rotating contact card on phone

- Part 1 of refactoring contact card fragment code,
Part 2 will be refactoring the tablet code to fix
bug 5082871

- ContactDetailActivity should always have non-null
fragments after onCreate() (either retrieved from
Fragment Manager or created dynamically)

- New view pager adapter that returns views that contain
existing fragments (instead of generating new fragments)

- The main reason for these changes is that when we
create fragments dynamically (necessary for the ViewPager
otherwise the fragment from XML will already have a parent),
the view ID will be the pager view ID. When we rotate,
the fragments are already defined in XML for the fragment
carousel and already have different view IDs, so it won't
restore properly. We can't put the pager view ID in the
fragment carousel layout, otherwise the ContactDetailActivity
will try to cast it to a ViewPager. Alternatively, if we make
both layouts rely on dynamically created fragments, the problem
becomes the fact that once fragments are added to the fragment manager,
they cannot be retrieved and added again to a different parent if there's a
different layout (exception is thrown). Thus, the solution is to have
the same parent container in both phone portrait and landscape layouts.

- Also it is unclear what was happening to the fragments on
rotation (they weren't being restored but weren't being removed
from the FragmentManager). We can remove the hack now that
would store the ViewPager fragment tags in the saved instance bundle
and manually remove those fragments from the FragmentManager after
an orientation change.

- In onCreate() of the ContactDetailActivity, if this is a
restored instance, then inflate the correct layout right away
so the fragments can find the parent containers.

- Save/restore ListView state on rotation

- Save/restore selected tab for contact with social updates

- Clean up computation code for FragmentCarousel by moving it to
onMeasure() method

Bug: 4976082
Bug: 4686406
Bug: 5082871

Change-Id: I7840b1dd1110da4dcc28ebabe3fc2739ff11c2f2
parent 629e3862
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<!--
  Container for the "About" page fragment on the contact card for a contact with social updates.
  This view ID must match with a view ID in the layout that is used after an orientation change.
-->
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/about_fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
 No newline at end of file
+14 −4
Original line number Diff line number Diff line
@@ -21,13 +21,23 @@
    android:scrollbars="none"
    android:orientation="horizontal">

    <fragment class="com.android.contacts.detail.ContactDetailFragment"
        android:id="@+id/about_fragment"
    <!--
      Container for the "About" page fragment on the contact card for a contact
      with social updates. This view ID must match with a view ID in the layout
      that is used after an orientation change.
    -->
    <FrameLayout
        android:id="@+id/about_fragment_container"
        android:layout_width="@dimen/detail_fragment_carousel_fragment_width"
        android:layout_height="match_parent" />

    <fragment class="com.android.contacts.detail.ContactDetailUpdatesFragment"
        android:id="@+id/updates_fragment"
    <!--
      Container for the "Updates" page fragment on the contact card for a contact
      with social updates. This view ID must match with a view ID in the layout
      that is used after an orientation change.
    -->
    <FrameLayout
        android:id="@+id/updates_fragment_container"
        android:layout_width="@dimen/detail_fragment_carousel_fragment_width"
        android:layout_height="match_parent" />

+25 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<!--
  Container for the "Updates" page fragment on the contact card for a contact with social updates.
  This view ID must match with a view ID in the layout that is used after an orientation change.
-->
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/updates_fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
 No newline at end of file
+105 −38
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.contacts.detail.ContactDetailFragment;
import com.android.contacts.detail.ContactDetailFragmentCarousel;
import com.android.contacts.detail.ContactDetailTabCarousel;
import com.android.contacts.detail.ContactDetailUpdatesFragment;
import com.android.contacts.detail.ContactDetailViewPagerAdapter;
import com.android.contacts.detail.ContactLoaderFragment;
import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener;
import com.android.contacts.interactions.ContactDeletionInteraction;
@@ -72,6 +73,8 @@ public class ContactDetailActivity extends ContactsActivity {
     */
    public static final String INTENT_KEY_IGNORE_DEFAULT_UP_BEHAVIOR = "ignoreDefaultUpBehavior";

    private static final String KEY_CONTACT_HAS_UPDATES = "contactHasUpdates";
    private static final String KEY_CURRENT_PAGE_INDEX = "currentPageIndex";
    private static final String KEY_DETAIL_FRAGMENT_TAG = "detailFragTag";
    private static final String KEY_UPDATES_FRAGMENT_TAG = "updatesFragTag";

@@ -90,6 +93,8 @@ public class ContactDetailActivity extends ContactsActivity {

    private ContactDetailFragmentCarousel mFragmentCarousel;

    private boolean mFragmentsAddedToFragmentManager;

    private ViewGroup mRootView;
    private ViewGroup mContentView;
    private LayoutInflater mInflater;
@@ -129,25 +134,37 @@ public class ContactDetailActivity extends ContactsActivity {
        mRootView = (ViewGroup) findViewById(R.id.contact_detail_view);
        mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        // Manually remove any {@link ViewPager} fragments if there was an orientation change
        // because the {@link ViewPager} is not used in both orientations. (If we leave the
        // fragments around, they'll be around in the {@link FragmentManager} but won't be visible
        // on screen and the {@link ViewPager} won't ask to initialize them again).
        if (savedState != null) {
            String aboutFragmentTag = savedState.getString(KEY_DETAIL_FRAGMENT_TAG);
            String updatesFragmentTag = savedState.getString(KEY_UPDATES_FRAGMENT_TAG);

        FragmentManager fragmentManager = getFragmentManager();
            mDetailFragment = (ContactDetailFragment) fragmentManager.findFragmentByTag(
                    aboutFragmentTag);
            mUpdatesFragment = (ContactDetailUpdatesFragment) fragmentManager.findFragmentByTag(
                    updatesFragmentTag);
        mDetailFragment = (ContactDetailFragment)
                fragmentManager.findFragmentByTag(
                ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
        mUpdatesFragment = (ContactDetailUpdatesFragment)
                fragmentManager.findFragmentByTag(
                ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);

        // If the fragment were found in the {@link FragmentManager} then we don't need to add
        // it again.
        if (mDetailFragment != null) {
            mFragmentsAddedToFragmentManager = true;
        } else {
            // Otherwise, create the fragments dynamically and remember to add them to the
            // {@link FragmentManager}.
            mDetailFragment = new ContactDetailFragment();
            mUpdatesFragment = new ContactDetailUpdatesFragment();
            mFragmentsAddedToFragmentManager = false;
        }

        mDetailFragment.setListener(mFragmentListener);
        mDetailFragment.setVerticalScrollListener(mVerticalScrollListener);
        mDetailFragment.setData(mLookupUri, mContactData);
        mUpdatesFragment.setData(mLookupUri, mContactData);

            if (mDetailFragment != null && mUpdatesFragment != null) {
                FragmentTransaction ft = fragmentManager.beginTransaction();
                ft.remove(mDetailFragment);
                ft.remove(mUpdatesFragment);
                ft.commit();
        if (savedState != null) {
            mContactHasUpdates = savedState.getBoolean(KEY_CONTACT_HAS_UPDATES);
            if (mContactHasUpdates) {
                setupContactWithUpdates(savedState.getInt(KEY_CURRENT_PAGE_INDEX, 0));
            } else {
                setupContactWithoutUpdates();
            }
        }

@@ -166,16 +183,9 @@ public class ContactDetailActivity extends ContactsActivity {

    @Override
    public void onAttachFragment(Fragment fragment) {
        if (fragment instanceof ContactDetailFragment) {
            mDetailFragment = (ContactDetailFragment) fragment;
            mDetailFragment.setListener(mFragmentListener);
            mDetailFragment.setVerticalScrollListener(mVerticalScrollListener);
            mDetailFragment.setData(mLookupUri, mContactData);
        } else if (fragment instanceof ContactDetailUpdatesFragment) {
            mUpdatesFragment = (ContactDetailUpdatesFragment) fragment;
            mUpdatesFragment.setData(mLookupUri, mContactData);
        } else if (fragment instanceof ContactLoaderFragment) {
         if (fragment instanceof ContactLoaderFragment) {
            mLoaderFragment = (ContactLoaderFragment) fragment;
            mLoaderFragment.setRetainInstance(true);
            mLoaderFragment.setListener(mLoaderFragmentListener);
            mLoaderFragment.loadUri(getIntent().getData());
        }
@@ -261,6 +271,8 @@ public class ContactDetailActivity extends ContactsActivity {
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(KEY_CONTACT_HAS_UPDATES, mContactHasUpdates);
        outState.putInt(KEY_CURRENT_PAGE_INDEX, getCurrentPage());
        if (mViewPager != null) {
            outState.putString(KEY_DETAIL_FRAGMENT_TAG, mDetailFragment.getTag());
            outState.putString(KEY_UPDATES_FRAGMENT_TAG, mUpdatesFragment.getTag());
@@ -296,7 +308,7 @@ public class ContactDetailActivity extends ContactsActivity {
                    invalidateOptionsMenu();
                    setupTitle();
                    if (mContactHasUpdates) {
                        setupContactWithUpdates();
                        setupContactWithUpdates(null /* Don't change the current page */);
                    } else {
                        setupContactWithoutUpdates();
                    }
@@ -328,7 +340,11 @@ public class ContactDetailActivity extends ContactsActivity {
        actionBar.setSubtitle(company);
    }

    private void setupContactWithUpdates() {
    /**
     * Setup the layout for the contact with updates. Pass in the index of the current page to
     * select or null if the current selection should be left as is.
     */
    private void setupContactWithUpdates(Integer currentPageIndex) {
        if (mContentView == null) {
            mContentView = (ViewGroup) mInflater.inflate(
                    R.layout.contact_detail_container_with_updates, mRootView, false);
@@ -337,29 +353,78 @@ public class ContactDetailActivity extends ContactsActivity {
            // Make sure all needed views are retrieved. Note that narrow width screens have a
            // {@link ViewPager} and {@link ContactDetailTabCarousel}, while wide width screens have
            // a {@link ContactDetailFragmentCarousel}.
            mTabCarousel = (ContactDetailTabCarousel) findViewById(R.id.tab_carousel);
            if (mTabCarousel != null) {
                mTabCarousel.setListener(mTabCarouselListener);
            }

            mViewPager = (ViewPager) findViewById(R.id.pager);
            if (mViewPager != null) {
                mViewPager.removeAllViews();
                mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
                // Inflate 2 view containers to pass in as children to the {@link ViewPager},
                // which will in turn be the parents to the mDetailFragment and mUpdatesFragment
                // since the fragments must have the same parent view IDs in both landscape and
                // portrait layouts.
                ViewGroup detailContainer = (ViewGroup) mInflater.inflate(
                        R.layout.contact_detail_about_fragment_container, mViewPager, false);
                ViewGroup updatesContainer = (ViewGroup) mInflater.inflate(
                        R.layout.contact_detail_updates_fragment_container, mViewPager, false);

                ContactDetailViewPagerAdapter adapter = new ContactDetailViewPagerAdapter();
                adapter.setAboutFragmentView(detailContainer);
                adapter.setUpdatesFragmentView(updatesContainer);

                mViewPager.addView(detailContainer);
                mViewPager.addView(updatesContainer);
                mViewPager.setAdapter(adapter);
                mViewPager.setOnPageChangeListener(mOnPageChangeListener);

                if (!mFragmentsAddedToFragmentManager) {
                    FragmentManager fragmentManager = getFragmentManager();
                    FragmentTransaction transaction = fragmentManager.beginTransaction();
                    transaction.add(R.id.about_fragment_container, mDetailFragment,
                            ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
                    transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
                            ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
                    transaction.commit();
                    fragmentManager.executePendingTransactions();
                }

            mTabCarousel = (ContactDetailTabCarousel) findViewById(R.id.tab_carousel);
            if (mTabCarousel != null) {
                mTabCarousel.setListener(mTabCarouselListener);
                // Select page if applicable
                if (currentPageIndex != null) {
                    mViewPager.setCurrentItem(currentPageIndex);
                }
            }

            mFragmentCarousel = (ContactDetailFragmentCarousel)
                    findViewById(R.id.fragment_carousel);
            // Add the fragments to the fragment containers in the carousel using a
            // {@link FragmentTransaction} if they haven't already been added to the
            // {@link FragmentManager}.
            if (mFragmentCarousel != null) {
                if (!mFragmentsAddedToFragmentManager) {
                    FragmentManager fragmentManager = getFragmentManager();
                    FragmentTransaction transaction = fragmentManager.beginTransaction();
                    transaction.add(R.id.about_fragment_container, mDetailFragment,
                            ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
                    transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
                            ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
                    transaction.commit();
                    fragmentManager.executePendingTransactions();
                }

                // Select page if applicable
                if (currentPageIndex != null) {
                    mFragmentCarousel.setCurrentPage(currentPageIndex);
                }
            }
        }

        // Then reset the contact data to the appropriate views
        if (mTabCarousel != null) {
            mTabCarousel.loadData(mContactData);
        }
        if (mFragmentCarousel != null) {
            if (mDetailFragment != null) mFragmentCarousel.setAboutFragment(mDetailFragment);
            if (mUpdatesFragment != null) mFragmentCarousel.setUpdatesFragment(mUpdatesFragment);
        if (mFragmentCarousel != null && mDetailFragment != null && mUpdatesFragment != null) {
            mFragmentCarousel.setFragments(mDetailFragment, mUpdatesFragment);
        }
        if (mDetailFragment != null) {
            mDetailFragment.setData(mLookupUri, mContactData);
@@ -374,6 +439,8 @@ public class ContactDetailActivity extends ContactsActivity {
            mContentView = (ViewGroup) mInflater.inflate(
                    R.layout.contact_detail_container_without_updates, mRootView, false);
            mRootView.addView(mContentView);
            mDetailFragment = (ContactDetailFragment) getFragmentManager().findFragmentById(
                    R.id.contact_detail_fragment);
        }
        // Reset contact data
        if (mDetailFragment != null) {
+22 −2
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ import android.net.ParseException;
import android.net.Uri;
import android.net.WebAddress;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.ContactsContract;
@@ -130,6 +131,7 @@ public class ContactDetailFragment extends Fragment implements FragmentKeyListen
    }

    private static final String KEY_CONTACT_URI = "contactUri";
    private static final String KEY_LIST_STATE = "liststate";
    private static final String LOADER_ARG_CONTACT_URI = "contactUri";

    private Context mContext;
@@ -192,6 +194,12 @@ public class ContactDetailFragment extends Fragment implements FragmentKeyListen
     */
    private View mTouchInterceptLayer;

    /**
     * Saved state of the {@link ListView}. This must be saved and applied to the {@ListView} only
     * when the adapter has been populated again.
     */
    private Parcelable mListState;

    /**
     * A list of distinct contact IDs included in the current contact.
     */
@@ -224,6 +232,7 @@ public class ContactDetailFragment extends Fragment implements FragmentKeyListen
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            mLookupUri = savedInstanceState.getParcelable(KEY_CONTACT_URI);
            mListState = savedInstanceState.getParcelable(KEY_LIST_STATE);
        }
        mNfcHandler = new NfcHandler(this);
    }
@@ -232,6 +241,9 @@ public class ContactDetailFragment extends Fragment implements FragmentKeyListen
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putParcelable(KEY_CONTACT_URI, mLookupUri);
        if (mListView != null) {
            outState.putParcelable(KEY_LIST_STATE, mListView.onSaveInstanceState());
        }
    }

    @Override
@@ -286,6 +298,7 @@ public class ContactDetailFragment extends Fragment implements FragmentKeyListen
        if (mContactData != null) {
            bindData();
        }

        return mView;
    }

@@ -415,9 +428,16 @@ public class ContactDetailFragment extends Fragment implements FragmentKeyListen
        if (mAdapter == null) {
            mAdapter = new ViewAdapter();
            mListView.setAdapter(mAdapter);
        } else {
            mAdapter.notifyDataSetChanged();
        }

        // Restore {@link ListView} state if applicable because the adapter is now populated.
        if (mListState != null) {
            mListView.onRestoreInstanceState(mListState);
            mListState = null;
        }

        mAdapter.notifyDataSetChanged();

        mListView.setEmptyView(mEmptyView);

        configureQuickFix();
Loading