Animate show/hide updates

Also fixes the vertical text position which was wrong due to the shadow

Bug:5268733
Bug:5204655

Change-Id: I011a482500e13b1b189c7e27dbcd40e2e1f42318
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index 2e59f71..b7cc87d 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -1265,6 +1265,21 @@
         mLoadInvitableAccountTypes = loadInvitableAccountTypes;
     }
 
+    /**
+     * Sets whether to load stream items. Will trigger a reload if the value has changed.
+     * At the moment, this is only used for debugging purposes
+     */
+    public void setLoadStreamItems(boolean value) {
+        if (mLoadStreamItems != value) {
+            mLoadStreamItems = value;
+            onContentChanged();
+        }
+    }
+
+    public boolean getLoadStreamItems() {
+        return mLoadStreamItems;
+    }
+
     public Uri getLookupUri() {
         return mLookupUri;
     }
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index b353a0b..601e9fb 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -43,6 +43,7 @@
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
@@ -56,6 +57,9 @@
 public class ContactDetailActivity extends ContactsActivity {
     private static final String TAG = "ContactDetailActivity";
 
+    /** Shows a toogle button for hiding/showing updates. Don't submit with true */
+    private static final boolean DEBUG_TRANSITIONS = false;
+
     /**
      * Boolean intent key that specifies whether pressing the "up" affordance in this activity
      * should cause it to finish itself or launch an intent to bring the user back to a specific
@@ -129,6 +133,19 @@
         super.onCreateOptionsMenu(menu);
         MenuInflater inflater = getMenuInflater();
         inflater.inflate(R.menu.star, menu);
+        if (DEBUG_TRANSITIONS) {
+            final MenuItem toggleSocial =
+                    menu.add(mLoaderFragment.getLoadStreamItems() ? "less" : "more");
+            toggleSocial.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            toggleSocial.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+                @Override
+                public boolean onMenuItemClick(MenuItem item) {
+                    mLoaderFragment.toggleLoadStreamItems();
+                    invalidateOptionsMenu();
+                    return false;
+                }
+            });
+        }
         return true;
     }
 
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index b87edbd..de08c4c 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -55,7 +55,6 @@
 import com.android.contacts.preference.DisplayOptionsPreferenceFragment;
 import com.android.contacts.util.AccountFilterUtil;
 import com.android.contacts.util.AccountPromptUtils;
-import com.android.contacts.util.AccountSelectionUtil;
 import com.android.contacts.util.AccountsListAdapter;
 import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
 import com.android.contacts.util.Constants;
@@ -88,6 +87,7 @@
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
@@ -111,6 +111,9 @@
 
     private static final String TAG = "PeopleActivity";
 
+    /** Shows a toogle button for hiding/showing updates. Don't submit with true */
+    private static final boolean DEBUG_TRANSITIONS = false;
+
     // These values needs to start at 2. See {@link ContactEntryListFragment}.
     private static final int SUBACTIVITY_NEW_CONTACT = 2;
     private static final int SUBACTIVITY_EDIT_CONTACT = 3;
@@ -1324,6 +1327,21 @@
             });
             addGroup.setActionView(mAddGroupImageView);
         }
+
+        if (DEBUG_TRANSITIONS && mContactDetailLoaderFragment != null) {
+            final MenuItem toggleSocial =
+                    menu.add(mContactDetailLoaderFragment.getLoadStreamItems() ? "less" : "more");
+            toggleSocial.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            toggleSocial.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+                @Override
+                public boolean onMenuItemClick(MenuItem item) {
+                    mContactDetailLoaderFragment.toggleLoadStreamItems();
+                    invalidateOptionsMenu();
+                    return false;
+                }
+            });
+        }
+
         return true;
     }
 
diff --git a/src/com/android/contacts/detail/CarouselTab.java b/src/com/android/contacts/detail/CarouselTab.java
index cdcf6b2..9331bed 100644
--- a/src/com/android/contacts/detail/CarouselTab.java
+++ b/src/com/android/contacts/detail/CarouselTab.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewPropertyAnimator;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
@@ -31,7 +32,10 @@
 
     private static final String TAG = CarouselTab.class.getSimpleName();
 
+    private static final long FADE_TRANSITION_TIME = 150;
+
     private TextView mLabelView;
+    private View mLabelBackgroundView;
 
     /**
      * This view adds an alpha layer over the entire tab.
@@ -55,6 +59,8 @@
         mLabelView = (TextView) findViewById(R.id.label);
         mLabelView.setClickable(true);
 
+        mLabelBackgroundView = findViewById(R.id.label_background);
+
         mAlphaLayer = findViewById(R.id.alpha_overlay);
         mTouchInterceptLayer = findViewById(R.id.touch_intercept_overlay);
     }
@@ -90,4 +96,20 @@
     public void setAlphaLayerValue(float alpha) {
         ContactDetailDisplayUtils.setAlphaOnViewBackground(mAlphaLayer, alpha);
     }
+
+    public void fadeInLabelViewAnimator(int startDelay, boolean fadeBackground) {
+        final ViewPropertyAnimator labelAnimator = mLabelView.animate();
+        mLabelView.setAlpha(0.0f);
+        labelAnimator.alpha(1.0f);
+        labelAnimator.setStartDelay(startDelay);
+        labelAnimator.setDuration(FADE_TRANSITION_TIME);
+
+        if (fadeBackground) {
+            final ViewPropertyAnimator backgroundAnimator = mLabelBackgroundView.animate();
+            mLabelBackgroundView.setAlpha(0.0f);
+            backgroundAnimator.alpha(1.0f);
+            backgroundAnimator.setStartDelay(startDelay);
+            backgroundAnimator.setDuration(FADE_TRANSITION_TIME);
+        }
+    }
 }
diff --git a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
index 756b1c7..0c3e6ac 100644
--- a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
@@ -23,6 +23,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewPropertyAnimator;
 import android.view.View.OnTouchListener;
 import android.widget.HorizontalScrollView;
 
@@ -84,7 +85,7 @@
     private ViewOverlay mAboutFragment;
     private ViewOverlay mUpdatesFragment;
 
-    private View mDetailFragmentView;
+    private View mAboutFragmentView;
     private View mUpdatesFragmentView;
 
     public ContactDetailFragmentCarousel(Context context) {
@@ -157,8 +158,8 @@
     /**
      * Set the view containers for the detail and updates fragment.
      */
-    public void setFragmentViews(View detailFragmentView, View updatesFragmentView) {
-        mDetailFragmentView = detailFragmentView;
+    public void setFragmentViews(View aboutFragmentView, View updatesFragmentView) {
+        mAboutFragmentView = aboutFragmentView;
         mUpdatesFragmentView = updatesFragmentView;
     }
 
@@ -180,7 +181,7 @@
             if (mUpdatesFragmentView != null) {
                 mUpdatesFragmentView.setVisibility(enable ? View.VISIBLE : View.GONE);
                 if (mCurrentPage == ABOUT_PAGE) {
-                    mDetailFragmentView.requestFocus();
+                    mAboutFragmentView.requestFocus();
                 } else {
                     mUpdatesFragmentView.requestFocus();
                 }
@@ -237,7 +238,7 @@
         if (!mEnableSwipe) {
             return;
         }
-        mLastScrollPosition= l;
+        mLastScrollPosition = l;
         updateAlphaLayers();
     }
 
@@ -283,4 +284,14 @@
         }
         return false;
     }
+
+    /**
+     * Starts an "appear" animation by moving in the "Updates" from the right.
+     */
+    public void animateAppear() {
+        final int x = Math.round((1.0f - FRAGMENT_WIDTH_SCREEN_WIDTH_FRACTION) * getWidth());
+        mUpdatesFragmentView.setTranslationX(x);
+        final ViewPropertyAnimator animator = mUpdatesFragmentView.animate();
+        animator.translationX(0.0f);
+    }
 }
diff --git a/src/com/android/contacts/detail/ContactDetailLayoutController.java b/src/com/android/contacts/detail/ContactDetailLayoutController.java
index 74811e4..cd479ca 100644
--- a/src/com/android/contacts/detail/ContactDetailLayoutController.java
+++ b/src/com/android/contacts/detail/ContactDetailLayoutController.java
@@ -65,6 +65,7 @@
     private final LayoutInflater mLayoutInflater;
     private final FragmentManager mFragmentManager;
 
+    private View mViewContainer;
     private ContactDetailFragment mDetailFragment;
     private ContactDetailUpdatesFragment mUpdatesFragment;
 
@@ -84,6 +85,7 @@
     private Uri mContactUri;
 
     private boolean mTabCarouselIsAnimating;
+
     private boolean mContactHasUpdates;
 
     private LayoutMode mLayoutMode;
@@ -104,6 +106,7 @@
         mContactDetailFragmentListener = contactDetailFragmentListener;
 
         // Retrieve views in case this is view pager and carousel mode
+        mViewContainer = viewContainer;
         mViewPager = (ViewPager) viewContainer.findViewById(R.id.pager);
         mTabCarousel = (ContactDetailTabCarousel) viewContainer.findViewById(R.id.tab_carousel);
 
@@ -228,7 +231,7 @@
         // Setup the layout if we already have a saved state
         if (savedState != null) {
             if (mContactHasUpdates) {
-                showContactWithUpdates();
+                showContactWithUpdates(false);
             } else {
                 showContactWithoutUpdates();
             }
@@ -236,10 +239,17 @@
     }
 
     public void setContactData(ContactLoader.Result data) {
+        final Boolean contactHadUpdates;
+        if (mContactData == null) {
+            contactHadUpdates = null;
+        } else {
+            contactHadUpdates = mContactHasUpdates;
+        }
         mContactData = data;
         mContactHasUpdates = !data.getStreamItems().isEmpty();
         if (mContactHasUpdates) {
-            showContactWithUpdates();
+            showContactWithUpdates(
+                    contactHadUpdates != null && contactHadUpdates.booleanValue() == false);
         } else {
             showContactWithoutUpdates();
         }
@@ -277,7 +287,7 @@
      * Setup the layout for the contact with updates.
      * TODO: Clean up this method so it's easier to understand.
      */
-    private void showContactWithUpdates() {
+    private void showContactWithUpdates(boolean animateStateChange) {
         if (mContactData == null) {
             return;
         }
@@ -288,6 +298,10 @@
 
         switch (mLayoutMode) {
             case TWO_COLUMN: {
+                // This is screen is very hard to animate properly, because there is such a hard
+                // cut from the regular version. A proper animation would have to reflow text and
+                // move things around. No animation for now
+
                 // Set the contact data (hide the static photo because the photo will already be in
                 // the header that scrolls with contact details).
                 mDetailFragment.setShowStaticPhoto(false);
@@ -307,11 +321,18 @@
                     resetViewPager();
                     resetTabCarousel();
                 }
+                if (!isDifferentContact && animateStateChange) {
+                    mTabCarousel.animateAppear(mViewContainer.getWidth(),
+                            mDetailFragment.getFirstListItemOffset());
+                }
                 break;
             }
             case FRAGMENT_CAROUSEL: {
                 // Allow swiping between all fragments
                 mFragmentCarousel.enableSwipe(true);
+                if (!isDifferentContact && animateStateChange) {
+                    mFragmentCarousel.animateAppear();
+                }
                 break;
             }
             default:
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index 186cedd..6cd48e3 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -20,13 +20,14 @@
 import com.android.contacts.R;
 import com.android.contacts.util.PhoneCapabilityTester;
 
-import android.app.Activity;
 import android.content.Context;
 import android.content.res.Resources;
 import android.util.AttributeSet;
+import android.util.TypedValue;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnTouchListener;
+import android.view.ViewPropertyAnimator;
 import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -39,6 +40,9 @@
 
     private static final String TAG = ContactDetailTabCarousel.class.getSimpleName();
 
+    private static final int TRANSITION_TIME = 200;
+    private static final int TRANSITION_MOVE_IN_TIME = 150;
+
     private static final int TAB_INDEX_ABOUT = 0;
     private static final int TAB_INDEX_UPDATES = 1;
     private static final int TAB_COUNT = 2;
@@ -55,14 +59,16 @@
     private ImageView mPhotoView;
     private TextView mStatusView;
     private ImageView mStatusPhotoView;
-    private boolean mHasPhoto;
     private OnClickListener mPhotoClickListener;
 
     private Listener mListener;
 
     private int mCurrentTab = TAB_INDEX_ABOUT;
 
+    private View mTabAndShadowContainer;
+    private View mShadow;
     private CarouselTab mAboutTab;
+    private View mTabDivider;
     private CarouselTab mUpdatesTab;
 
     /** Last Y coordinate of the carousel when the tab at the given index was selected */
@@ -107,19 +113,26 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mTabAndShadowContainer = findViewById(R.id.tab_and_shadow_container);
         mAboutTab = (CarouselTab) findViewById(R.id.tab_about);
         mAboutTab.setLabel(mContext.getString(R.string.contactDetailAbout));
 
+        mTabDivider = findViewById(R.id.tab_divider);
+
         mUpdatesTab = (CarouselTab) findViewById(R.id.tab_update);
         mUpdatesTab.setLabel(mContext.getString(R.string.contactDetailUpdates));
 
         mAboutTab.enableTouchInterceptor(mAboutTabTouchInterceptListener);
         mUpdatesTab.enableTouchInterceptor(mUpdatesTabTouchInterceptListener);
 
+        mShadow = findViewById(R.id.shadow);
+
         // Retrieve the photo view for the "about" tab
+        // TODO: This should be moved down to mAboutTab, so that it hosts its own controls
         mPhotoView = (ImageView) mAboutTab.findViewById(R.id.photo);
 
         // Retrieve the social update views for the "updates" tab
+        // TODO: This should be moved down to mUpdatesTab, so that it hosts its own controls
         mStatusView = (TextView) mUpdatesTab.findViewById(R.id.status);
         mStatusPhotoView = (ImageView) mUpdatesTab.findViewById(R.id.status_photo);
     }
@@ -128,22 +141,31 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int screenWidth = MeasureSpec.getSize(widthMeasureSpec);
         // Compute the width of a tab as a fraction of the screen width
-        int tabWidth = (int) (mTabWidthScreenWidthFraction * screenWidth);
+        int tabWidth = Math.round(mTabWidthScreenWidthFraction * screenWidth);
 
         // Find the allowed scrolling length by subtracting the current visible screen width
         // from the total length of the tabs.
         mAllowedHorizontalScrollLength = tabWidth * TAB_COUNT - screenWidth;
 
-        int tabHeight = (int) (screenWidth * mTabHeightScreenWidthFraction) + mTabShadowHeight;
+        int tabHeight = Math.round(screenWidth * mTabHeightScreenWidthFraction) + mTabShadowHeight;
         // Set the child {@link LinearLayout} to be TAB_COUNT * the computed tab width so that the
         // {@link LinearLayout}'s children (which are the tabs) will evenly split that width.
         if (getChildCount() > 0) {
             View child = getChildAt(0);
-            child.measure(MeasureSpec.makeMeasureSpec(TAB_COUNT * tabWidth, MeasureSpec.EXACTLY),
+
+            // add 1 dip of seperation between the tabs
+            final int seperatorPixels =
+                    (int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1,
+                    getResources().getDisplayMetrics()) + 0.5f);
+
+            child.measure(
+                    MeasureSpec.makeMeasureSpec(
+                            TAB_COUNT * tabWidth +
+                            (TAB_COUNT - 1) * seperatorPixels, MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(tabHeight, MeasureSpec.EXACTLY));
         }
 
-        mAllowedVerticalScrollLength = tabHeight - mTabDisplayLabelHeight;
+        mAllowedVerticalScrollLength = tabHeight - mTabDisplayLabelHeight - mTabShadowHeight;
         setMeasuredDimension(
                 resolveSize(screenWidth, widthMeasureSpec),
                 resolveSize(tabHeight, heightMeasureSpec));
@@ -177,6 +199,105 @@
         }
     };
 
+    /**
+     * Does in "appear" animation to allow a seamless transition from
+     * the "No updates" mode.
+     * @param width Width of the container. As we haven't been layed out yet, we can't know
+     * @param scrollOffset The offset by how far we scrolled, where 0=not scrolled, -x=scrolled by
+     * x pixels, Integer.MIN_VALUE=scrolled so far that the image is not visible in "no updates"
+     * mode of this screen
+     */
+    public void animateAppear(int width, int scrollOffset) {
+        final float photoHeight = mTabHeightScreenWidthFraction * width;
+        final boolean animateZoomAndFade;
+        int pixelsToScrollVertically = 0;
+
+        // Depending on how far we are scrolled down, there is one of three animations:
+        //   - Zoom and fade the picture (if it is still visible)
+        //   - Scroll, zoom and fade (if the picture is mostly invisible and we now have a
+        //     bigger visible region due to the pinning)
+        //   - Just scroll if the picture is completely invisible. This time, no zoom is needed
+        if (scrollOffset == Integer.MIN_VALUE) {
+            // animate in completely by scrolling. no need for zooming here
+            pixelsToScrollVertically = mTabDisplayLabelHeight;
+            animateZoomAndFade = false;
+        } else {
+            final int pixelsOfPhotoLeft = Math.round(photoHeight) + scrollOffset;
+            if (pixelsOfPhotoLeft > mTabDisplayLabelHeight) {
+                // nothing to scroll
+                pixelsToScrollVertically = 0;
+            } else {
+                pixelsToScrollVertically = mTabDisplayLabelHeight - pixelsOfPhotoLeft;
+            }
+            animateZoomAndFade = true;
+        }
+
+        if (pixelsToScrollVertically != 0) {
+            // We can't animate ourselves here, because our own translation is needed for the user's
+            // scrolling. Instead, we use our only child. As we are transparent, that is just as
+            // good
+            mTabAndShadowContainer.setTranslationY(-pixelsToScrollVertically);
+            final ViewPropertyAnimator animator = mTabAndShadowContainer.animate();
+            animator.translationY(0.0f);
+            animator.setDuration(TRANSITION_MOVE_IN_TIME);
+        }
+
+        if (animateZoomAndFade) {
+            // Hack: We have two types of possible layouts:
+            //   If the picture is square, it is square in both "with updates" and "without updates"
+            //     --> no need for scale animation here
+            //     example: 10inch tablet portrait
+            //   If the picture is non-square, it is full-width in "without updates" and something
+            //     arbitrary in "with updates"
+            //     --> do animation with container
+            //     example: 4.6inch phone portrait
+            final boolean squarePicture =
+                    mTabWidthScreenWidthFraction == mTabHeightScreenWidthFraction;
+            final int firstTransitionTime;
+            if (squarePicture) {
+                firstTransitionTime = 0;
+            } else {
+                // For x, we need to scale our container so we'll animate the whole tab
+                // (unfortunately, we need to have the text invisible during this transition as it
+                // would also be stretched)
+                float revScale = 1.0f/mTabWidthScreenWidthFraction;
+                mAboutTab.setScaleX(revScale);
+                mAboutTab.setPivotX(0.0f);
+                final ViewPropertyAnimator aboutAnimator = mAboutTab.animate();
+                aboutAnimator.setDuration(TRANSITION_TIME);
+                aboutAnimator.scaleX(1.0f);
+
+                // For y, we need to scale only the picture itself because we want it to be cropped
+                mPhotoView.setScaleY(revScale);
+                mPhotoView.setPivotY(photoHeight * 0.5f);
+                final ViewPropertyAnimator photoAnimator = mPhotoView.animate();
+                photoAnimator.setDuration(TRANSITION_TIME);
+                photoAnimator.scaleY(1.0f);
+                firstTransitionTime = TRANSITION_TIME;
+            }
+
+            // Animate in the labels after the above transition is finished
+            mAboutTab.fadeInLabelViewAnimator(firstTransitionTime, true);
+            mUpdatesTab.fadeInLabelViewAnimator(firstTransitionTime, false);
+
+            final float pixelsToTranslate = (1.0f - mTabWidthScreenWidthFraction) * width;
+            // Views to translate
+            for (View view : new View[] { mUpdatesTab, mTabDivider }) {
+                view.setTranslationX(pixelsToTranslate);
+                final ViewPropertyAnimator translateAnimator = view.animate();
+                translateAnimator.translationX(0.0f);
+                translateAnimator.setDuration(TRANSITION_TIME);
+            }
+
+            // Another hack: If the picture is square, there is no shadow in "Without updates"
+            //    --> fade it in after the translations are done
+            if (squarePicture) {
+                mShadow.setAlpha(0.0f);
+                mShadow.animate().setStartDelay(TRANSITION_TIME).alpha(1.0f);
+            }
+        }
+    }
+
     private void updateAlphaLayers() {
         mAboutTab.setAlphaLayerValue(mLastScrollPosition * MAX_ALPHA /
                 mAllowedHorizontalScrollLength);
@@ -290,7 +411,6 @@
         if (contactData == null) {
             return;
         }
-        mHasPhoto = contactData.getPhotoUri() != null;
 
         // TODO: Move this into the {@link CarouselTab} class when the updates fragment code is more
         // finalized
diff --git a/src/com/android/contacts/detail/ContactLoaderFragment.java b/src/com/android/contacts/detail/ContactLoaderFragment.java
index 008aff8..ddccfe6 100644
--- a/src/com/android/contacts/detail/ContactLoaderFragment.java
+++ b/src/com/android/contacts/detail/ContactLoaderFragment.java
@@ -407,4 +407,18 @@
                 mContext, mLookupUri, mCustomRingtone);
         mContext.startService(intent);
     }
+
+    /** Toggles whether to load stream items. Just for debugging */
+    public void toggleLoadStreamItems() {
+        Loader<ContactLoader.Result> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS);
+        ContactLoader loader = (ContactLoader) loaderObj;
+        loader.setLoadStreamItems(!loader.getLoadStreamItems());
+    }
+
+    /** Returns whether to load stream items. Just for debugging */
+    public boolean getLoadStreamItems() {
+        Loader<ContactLoader.Result> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS);
+        ContactLoader loader = (ContactLoader) loaderObj;
+        return loader != null && loader.getLoadStreamItems();
+    }
 }