Merge "Use ContactsContract.PhoneLookup to look up SIP addresses." into ics-mr1
diff --git a/src/com/android/contacts/ContactsApplication.java b/src/com/android/contacts/ContactsApplication.java
index f806c1d..16f9e57 100644
--- a/src/com/android/contacts/ContactsApplication.java
+++ b/src/com/android/contacts/ContactsApplication.java
@@ -23,6 +23,8 @@
 import com.google.common.annotations.VisibleForTesting;
 
 import android.app.Application;
+import android.app.FragmentManager;
+import android.app.LoaderManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -31,6 +33,9 @@
 import android.util.Log;
 
 public final class ContactsApplication extends Application {
+    private static final boolean ENABLE_LOADER_LOG = false; // Don't submit with true
+    private static final boolean ENABLE_FRAGMENT_LOG = false; // Don't submit with true
+
     private static InjectedServices sInjectedServices;
     private AccountTypeManager mAccountTypeManager;
     private ContactPhotoManager mContactPhotoManager;
@@ -118,6 +123,8 @@
         Context context = getApplicationContext();
         PreferenceManager.getDefaultSharedPreferences(context);
         AccountTypeManager.getInstance(context);
+        if (ENABLE_FRAGMENT_LOG) FragmentManager.enableDebugLogging(true);
+        if (ENABLE_LOADER_LOG) LoaderManager.enableDebugLogging(true);
 
         if (Log.isLoggable(Constants.STRICT_MODE_TAG, Log.DEBUG)) {
             StrictMode.setThreadPolicy(
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 96136a2..88aab4d 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -20,6 +20,7 @@
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
 import com.android.contacts.activities.DialtactsActivity.ViewPagerVisibilityListener;
+import com.android.contacts.util.EmptyLoader;
 import com.android.contacts.voicemail.VoicemailStatusHelper;
 import com.android.contacts.voicemail.VoicemailStatusHelper.StatusMessage;
 import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
@@ -59,6 +60,11 @@
         CallLogQueryHandler.Listener, CallLogAdapter.CallFetcher {
     private static final String TAG = "CallLogFragment";
 
+    /**
+     * ID of the empty loader to defer other fragments.
+     */
+    private static final int EMPTY_LOADER_ID = 0;
+
     private CallLogAdapter mAdapter;
     private CallLogQueryHandler mCallLogQueryHandler;
     private boolean mScrollToTop;
@@ -75,6 +81,10 @@
     private TextView mStatusMessageAction;
     private KeyguardManager mKeyguardManager;
 
+    private boolean mEmptyLoaderRunning;
+    private boolean mCallLogFetched;
+    private boolean mVoicemailStatusFetched;
+
     @Override
     public void onCreate(Bundle state) {
         super.onCreate(state);
@@ -103,6 +113,8 @@
             listView.smoothScrollToPosition(0);
             mScrollToTop = false;
         }
+        mCallLogFetched = true;
+        destroyEmptyLoaderIfAllDataFetched();
     }
 
     /**
@@ -118,6 +130,15 @@
         int activeSources = mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor);
         setVoicemailSourcesAvailable(activeSources != 0);
         MoreCloseables.closeQuietly(statusCursor);
+        mVoicemailStatusFetched = true;
+        destroyEmptyLoaderIfAllDataFetched();
+    }
+
+    private void destroyEmptyLoaderIfAllDataFetched() {
+        if (mCallLogFetched && mVoicemailStatusFetched && mEmptyLoaderRunning) {
+            mEmptyLoaderRunning = false;
+            getLoaderManager().destroyLoader(EMPTY_LOADER_ID);
+        }
     }
 
     /** Sets whether there are any voicemail sources available in the platform. */
@@ -155,6 +176,12 @@
     @Override
     public void onStart() {
         mScrollToTop = true;
+
+        // Start the empty loader now to defer other fragments.  We destroy it when both calllog
+        // and the voicemail status are fetched.
+        getLoaderManager().initLoader(EMPTY_LOADER_ID, null,
+                new EmptyLoader.Callback(getActivity()));
+        mEmptyLoaderRunning = true;
         super.onStart();
     }
 
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 5d7ab93..e63ee28 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -387,6 +387,16 @@
     }
 
     /**
+     * Reset the list adapter in this {@link Fragment} to get rid of any saved scroll position
+     * from a previous contact.
+     */
+    public void resetAdapter() {
+        if (mListView != null) {
+            mListView.setAdapter(mAdapter);
+        }
+    }
+
+    /**
      * Returns the top coordinate of the first item in the {@link ListView}. If the first item
      * in the {@link ListView} is not visible or there are no children in the list, then return
      * Integer.MIN_VALUE. Note that the returned value will be <= 0 because the first item in the
diff --git a/src/com/android/contacts/detail/ContactDetailLayoutController.java b/src/com/android/contacts/detail/ContactDetailLayoutController.java
index 2bcd1a0..74811e4 100644
--- a/src/com/android/contacts/detail/ContactDetailLayoutController.java
+++ b/src/com/android/contacts/detail/ContactDetailLayoutController.java
@@ -20,6 +20,7 @@
 import com.android.contacts.NfcHandler;
 import com.android.contacts.R;
 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
+import com.android.contacts.util.UriUtils;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
@@ -28,6 +29,7 @@
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.content.Context;
+import android.net.Uri;
 import android.os.Bundle;
 import android.support.v4.view.ViewPager;
 import android.support.v4.view.ViewPager.OnPageChangeListener;
@@ -42,6 +44,7 @@
  */
 public class ContactDetailLayoutController {
 
+    private static final String KEY_CONTACT_URI = "contactUri";
     private static final String KEY_CONTACT_HAS_UPDATES = "contactHasUpdates";
     private static final String KEY_CURRENT_PAGE_INDEX = "currentPageIndex";
 
@@ -78,6 +81,7 @@
     private ContactDetailFragment.Listener mContactDetailFragmentListener;
 
     private ContactLoader.Result mContactData;
+    private Uri mContactUri;
 
     private boolean mTabCarouselIsAnimating;
     private boolean mContactHasUpdates;
@@ -144,6 +148,7 @@
         // Read from savedState if possible
         int currentPageIndex = 0;
         if (savedState != null) {
+            mContactUri = savedState.getParcelable(KEY_CONTACT_URI);
             mContactHasUpdates = savedState.getBoolean(KEY_CONTACT_HAS_UPDATES);
             currentPageIndex = savedState.getInt(KEY_CURRENT_PAGE_INDEX, 0);
         }
@@ -269,13 +274,18 @@
     }
 
     /**
-     * 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.
+     * Setup the layout for the contact with updates.
+     * TODO: Clean up this method so it's easier to understand.
      */
     private void showContactWithUpdates() {
         if (mContactData == null) {
             return;
         }
+
+        Uri previousContactUri = mContactUri;
+        mContactUri = mContactData.getLookupUri();
+        boolean isDifferentContact = !UriUtils.areEqual(previousContactUri, mContactUri);
+
         switch (mLayoutMode) {
             case TWO_COLUMN: {
                 // Set the contact data (hide the static photo because the photo will already be in
@@ -292,6 +302,11 @@
                 mTabCarousel.setVisibility(View.VISIBLE);
                 // Update ViewPager to allow swipe between all the fragments (to see updates)
                 mViewPagerAdapter.enableSwipe(true);
+                // If this is a different contact than before, then reset some views.
+                if (isDifferentContact) {
+                    resetViewPager();
+                    resetTabCarousel();
+                }
                 break;
             }
             case FRAGMENT_CAROUSEL: {
@@ -303,14 +318,27 @@
                 throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
         }
 
-        mDetailFragment.setData(mContactData.getLookupUri(), mContactData);
-        mUpdatesFragment.setData(mContactData.getLookupUri(), mContactData);
+        if (isDifferentContact) {
+            resetFragments();
+        }
+
+        mDetailFragment.setData(mContactUri, mContactData);
+        mUpdatesFragment.setData(mContactUri, mContactData);
     }
 
+    /**
+     * Setup the layout for the contact without updates.
+     * TODO: Clean up this method so it's easier to understand.
+     */
     private void showContactWithoutUpdates() {
         if (mContactData == null) {
             return;
         }
+
+        Uri previousContactUri = mContactUri;
+        mContactUri = mContactData.getLookupUri();
+        boolean isDifferentContact = !UriUtils.areEqual(previousContactUri, mContactUri);
+
         switch (mLayoutMode) {
             case TWO_COLUMN:
                 // Show the static photo which is next to the list of scrolling contact details
@@ -336,7 +364,24 @@
                 throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
         }
 
-        mDetailFragment.setData(mContactData.getLookupUri(), mContactData);
+        if (isDifferentContact) {
+            resetFragments();
+        }
+
+        mDetailFragment.setData(mContactUri, mContactData);
+    }
+
+    private void resetTabCarousel() {
+        mTabCarousel.reset();
+    }
+
+    private void resetViewPager() {
+        mViewPager.setCurrentItem(0, false /* smooth transition */);
+    }
+
+    private void resetFragments() {
+        mDetailFragment.resetAdapter();
+        mUpdatesFragment.resetAdapter();
     }
 
     public FragmentKeyListener getCurrentPage() {
@@ -365,6 +410,7 @@
     }
 
     public void onSaveInstanceState(Bundle outState) {
+        outState.putParcelable(KEY_CONTACT_URI, mContactUri);
         outState.putBoolean(KEY_CONTACT_HAS_UPDATES, mContactHasUpdates);
         outState.putInt(KEY_CURRENT_PAGE_INDEX, getCurrentPageIndex());
     }
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index 4700078..045e900 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -180,6 +180,16 @@
     }
 
     /**
+     * Reset the carousel to the start position (i.e. because new data will be loaded in for a
+     * different contact).
+     */
+    public void reset() {
+        scrollTo(0, 0);
+        setCurrentTab(0);
+        moveToYCoordinate(0, 0);
+    }
+
+    /**
      * Set the current tab that should be restored when the view is first laid out.
      */
     public void restoreCurrentTab(int position) {
diff --git a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
index afe159b..fd59674 100644
--- a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
@@ -158,6 +158,14 @@
         }
     }
 
+    /**
+     * Reset the list adapter in this {@link Fragment} to get rid of any saved scroll position
+     * from a previous contact.
+     */
+    public void resetAdapter() {
+        setListAdapter(mStreamItemAdapter);
+    }
+
     @Override
     public void setAlphaLayerValue(float alpha) {
         // If the alpha layer is not ready yet, store it for later when the view is initialized
diff --git a/src/com/android/contacts/group/GroupDetailFragment.java b/src/com/android/contacts/group/GroupDetailFragment.java
index 5b275bd..f0e4175 100644
--- a/src/com/android/contacts/group/GroupDetailFragment.java
+++ b/src/com/android/contacts/group/GroupDetailFragment.java
@@ -193,7 +193,6 @@
      * Start the loader to retrieve the metadata for this group.
      */
     private void startGroupMetadataLoader() {
-        getLoaderManager().destroyLoader(LOADER_METADATA);
         getLoaderManager().restartLoader(LOADER_METADATA, null, mGroupMetadataLoaderListener);
     }
 
@@ -201,7 +200,6 @@
      * Start the loader to retrieve the list of group members.
      */
     private void startGroupMembersLoader() {
-        getLoaderManager().destroyLoader(LOADER_MEMBERS);
         getLoaderManager().restartLoader(LOADER_MEMBERS, null, mGroupMemberListLoaderListener);
     }
 
diff --git a/src/com/android/contacts/util/Constants.java b/src/com/android/contacts/util/Constants.java
index 3a43c40..eb68f5a 100644
--- a/src/com/android/contacts/util/Constants.java
+++ b/src/com/android/contacts/util/Constants.java
@@ -32,18 +32,6 @@
     public static final String PERFORMANCE_TAG = "ContactsPerf";
 
     /**
-     * Log tag for enabling/disabling LoaderManager log.
-     * To enable: adb shell setprop log.tag.ContactsLoaderManager DEBUG
-     */
-    public static final String LOADER_MANAGER_TAG = "ContactsLoaderManager";
-
-    /**
-     * Log tag for enabling/disabling FragmentManager log.
-     * To enable: adb shell setprop log.tag.ContactsFragmentManager DEBUG
-     */
-    public static final String FRAGMENT_MANAGER_TAG = "ContactsFragmentManager";
-
-    /**
      * Log tag for enabling/disabling StrictMode violation log.
      * To enable: adb shell setprop log.tag.ContactsStrictMode DEBUG
      */
diff --git a/src/com/android/contacts/util/EmptyLoader.java b/src/com/android/contacts/util/EmptyLoader.java
new file mode 100644
index 0000000..97478bd
--- /dev/null
+++ b/src/com/android/contacts/util/EmptyLoader.java
@@ -0,0 +1,60 @@
+/*
+ * 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
+ */
+
+package com.android.contacts.util;
+
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.Loader;
+import android.os.Bundle;
+
+/**
+ * A {@link Loader} only used to make use of the {@link android.app.Fragment#setStartDeferred}
+ * feature from an old-style fragment which doesn't use {@link Loader}s to load data.
+ *
+ * This loader never delivers results.  A caller fragment must destroy it when deferred fragments
+ * should be started.
+ */
+public class EmptyLoader extends Loader<Object> {
+    public EmptyLoader(Context context) {
+        super(context);
+    }
+
+    /**
+     * {@link LoaderCallbacks} which just generates {@link EmptyLoader}.  {@link #onLoadFinished}
+     * and {@link #onLoaderReset} are no-op.
+     */
+    public static class Callback implements LoaderCallbacks<Object> {
+        private final Context mContext;
+
+        public Callback(Context context) {
+            mContext = context.getApplicationContext();
+        }
+
+        @Override
+        public Loader<Object> onCreateLoader(int id, Bundle args) {
+            return new EmptyLoader(mContext);
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Object> loader, Object data) {
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Object> loader) {
+        }
+    }
+}