Merge "Introduce a "loading" view for phove favorite"
diff --git a/res/layout/phone_loading_contacts.xml b/res/layout/phone_loading_contacts.xml
new file mode 100644
index 0000000..f0d3328
--- /dev/null
+++ b/res/layout/phone_loading_contacts.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<!-- "Loading" text with a spinner, which is used in PhoneFavorite screen -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:orientation="horizontal"
+    android:gravity="left|center_vertical">
+
+    <ProgressBar
+        android:indeterminate="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/progress_spinner"/>
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/contact_list_loading"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_marginLeft="4dip" />
+
+</LinearLayout>
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 79dec09..e8a6367 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -149,7 +149,7 @@
         final ContactListFilter filter = getFilter();
         if (filter != null && !isSearchMode()) {
             final boolean shouldShowHeader = AccountFilterUtil.updateAccountFilterTitleForPeople(
-                    mAccountFilterHeader, filter, false, false);
+                    mAccountFilterHeader, filter, false);
             mAccountFilterHeader.setVisibility(shouldShowHeader ? View.VISIBLE : View.GONE);
         } else {
             mAccountFilterHeader.setVisibility(View.GONE);
diff --git a/src/com/android/contacts/list/PhoneFavoriteFragment.java b/src/com/android/contacts/list/PhoneFavoriteFragment.java
index 6ed846d..a8eafdc 100644
--- a/src/com/android/contacts/list/PhoneFavoriteFragment.java
+++ b/src/com/android/contacts/list/PhoneFavoriteFragment.java
@@ -26,7 +26,6 @@
 import android.app.Activity;
 import android.app.Fragment;
 import android.app.LoaderManager;
-import android.content.Context;
 import android.content.CursorLoader;
 import android.content.Intent;
 import android.content.Loader;
@@ -34,6 +33,8 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Directory;
 import android.provider.Settings;
@@ -132,7 +133,8 @@
             if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onLoadFinished");
             mAllContactsAdapter.changeCursor(0, data);
             updateFilterHeaderView();
-            mAccountFilterHeaderContainer.setVisibility(View.VISIBLE);
+            mHandler.removeMessages(MESSAGE_SHOW_LOADING_EFFECT);
+            mLoadingView.setVisibility(View.VISIBLE);
         }
 
         @Override
@@ -202,6 +204,19 @@
         }
     }
 
+    private static final int MESSAGE_SHOW_LOADING_EFFECT = 1;
+    private static final int LOADING_EFFECT_DELAY = 500;  // ms
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_SHOW_LOADING_EFFECT:
+                    mLoadingView.setVisibility(View.VISIBLE);
+                    break;
+            }
+        }
+    };
+
     private Listener mListener;
     private PhoneFavoriteMergedAdapter mAdapter;
     private ContactTileAdapter mContactTileAdapter;
@@ -229,6 +244,12 @@
     private FrameLayout mAccountFilterHeaderContainer;
     private View mAccountFilterHeader;
 
+    /**
+     * Layout used when contacts load is slower than expected and thus "loading" view should be
+     * shown.
+     */
+    private View mLoadingView;
+
     private final ContactTileView.Listener mContactTileAdapterListener =
             new ContactTileAdapterListener();
     private final LoaderManager.LoaderCallbacks<Cursor> mContactTileLoaderListener =
@@ -323,10 +344,12 @@
                 mListView, false);
         mAccountFilterHeader.setOnClickListener(mFilterHeaderClickListener);
         mAccountFilterHeaderContainer.addView(mAccountFilterHeader);
-        mAccountFilterHeaderContainer.setVisibility(View.GONE);
+
+        mLoadingView = inflater.inflate(R.layout.phone_loading_contacts, mListView, false);
 
         mAdapter = new PhoneFavoriteMergedAdapter(getActivity(),
-                mContactTileAdapter, mAccountFilterHeaderContainer, mAllContactsAdapter);
+                mContactTileAdapter, mAccountFilterHeaderContainer, mAllContactsAdapter,
+                mLoadingView);
 
         mListView.setAdapter(mAdapter);
 
@@ -411,6 +434,12 @@
         // This method call implicitly assures ContactTileLoaderListener's onLoadFinished() will
         // be called, on which we'll check if "all" contacts should be reloaded again or not.
         getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, mContactTileLoaderListener);
+
+        // Delay showing "loading" view until certain amount of time so that users won't see
+        // instant flash of the view when the contacts load is fast enough.
+        // This will be kept shown until both tile and all sections are loaded.
+        mLoadingView.setVisibility(View.INVISIBLE);
+        mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_LOADING_EFFECT, LOADING_EFFECT_DELAY);
     }
 
     @Override
@@ -502,8 +531,7 @@
         if (mAccountFilterHeader == null || mAllContactsAdapter == null || filter == null) {
             return;
         }
-        AccountFilterUtil.updateAccountFilterTitleForPhone(
-                mAccountFilterHeader, filter, mAllContactsAdapter.isLoading(), true);
+        AccountFilterUtil.updateAccountFilterTitleForPhone(mAccountFilterHeader, filter, true);
     }
 
     public ContactListFilter getFilter() {
diff --git a/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java b/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
index 205e156..b35d446 100644
--- a/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
+++ b/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
@@ -44,6 +44,7 @@
     private final ContactTileAdapter mContactTileAdapter;
     private final ContactEntryListAdapter mContactEntryListAdapter;
     private final View mAccountFilterHeaderContainer;
+    private final View mLoadingView;
 
     private final int mItemPaddingLeft;
     private final int mItemPaddingRight;
@@ -56,7 +57,8 @@
     public PhoneFavoriteMergedAdapter(Context context,
             ContactTileAdapter contactTileAdapter,
             View accountFilterHeaderContainer,
-            ContactEntryListAdapter contactEntryListAdapter) {
+            ContactEntryListAdapter contactEntryListAdapter,
+            View loadingView) {
         Resources resources = context.getResources();
         mItemPaddingLeft = resources.getDimensionPixelSize(R.dimen.detail_item_side_margin);
         mItemPaddingRight = resources.getDimensionPixelSize(R.dimen.list_visible_scrollbar_padding);
@@ -70,6 +72,8 @@
         mObserver = new CustomDataSetObserver();
         mContactTileAdapter.registerDataSetObserver(mObserver);
         mContactEntryListAdapter.registerDataSetObserver(mObserver);
+
+        mLoadingView = loadingView;
     }
 
     @Override
@@ -77,9 +81,12 @@
         final int contactTileAdapterCount = mContactTileAdapter.getCount();
         final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
         if (mContactEntryListAdapter.isLoading()) {
-            // Hide "all" contacts during its being loaded.
-            return contactTileAdapterCount + 1;
+            // Hide "all" contacts during its being loaded. Instead show "loading" view.
+            //
+            // "+2" for mAccountFilterHeaderContainer and mLoadingView
+            return contactTileAdapterCount + 2;
         } else {
+            // "+1" for mAccountFilterHeaderContainer
             return contactTileAdapterCount + contactEntryListAdapterCount + 1;
         }
     }
@@ -88,13 +95,18 @@
     public Object getItem(int position) {
         final int contactTileAdapterCount = mContactTileAdapter.getCount();
         final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
-        if (position < contactTileAdapterCount) {
+        if (position < contactTileAdapterCount) {  // For "tile" and "frequent" sections
             return mContactTileAdapter.getItem(position);
-        } else if (position == contactTileAdapterCount) {
+        } else if (position == contactTileAdapterCount) {  // For "all" section's account header
             return mAccountFilterHeaderContainer;
-        } else {
-            final int localPosition = position - contactTileAdapterCount - 1;
-            return mContactTileAdapter.getItem(localPosition);
+        } else {  // For "all" section
+            if (mContactEntryListAdapter.isLoading()) {  // "All" section is being loaded.
+                return mLoadingView;
+            } else {
+                // "-1" for mAccountFilterHeaderContainer
+                final int localPosition = position - contactTileAdapterCount - 1;
+                return mContactTileAdapter.getItem(localPosition);
+            }
         }
     }
 
@@ -105,25 +117,63 @@
 
     @Override
     public int getViewTypeCount() {
+        // "+2" for mAccountFilterHeaderContainer and mLoadingView
         return (mContactTileAdapter.getViewTypeCount()
                 + mContactEntryListAdapter.getViewTypeCount()
-                + 1);
+                + 2);
     }
 
     @Override
     public int getItemViewType(int position) {
         final int contactTileAdapterCount = mContactTileAdapter.getCount();
         final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
-        if (position < contactTileAdapterCount) {
+        // There should be four kinds of types that are usually used, and one more exceptional
+        // type (IGNORE_ITEM_VIEW_TYPE), which sometimes comes from mContactTileAdapter.
+        //
+        // The four ordinary view types have the index equal to or more than 0, and less than
+        // mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount() + 2.
+        // (See also this class's getViewTypeCount())
+        //
+        // We have those values for:
+        // - The view types mContactTileAdapter originally has
+        // - The view types mContactEntryListAdapter originally has
+        // - mAccountFilterHeaderContainer ("all" section's account header), and
+        // - mLoadingView
+        //
+        // Those types should not be mixed, so we have a different range for each kinds of types:
+        // - Types for mContactTileAdapter ("tile" and "frequent" sections)
+        //   They should have the index, >=0 and <mContactTileAdapter.getViewTypeCount()
+        //
+        // - Types for mContactEntryListAdapter ("all" sections)
+        //   They should have the index, >=mContactTileAdapter.getViewTypeCount() and
+        //   <(mContactTileAdapter.getViewTypeCount() + mContactEntryListAdapter.getViewTypeCount())
+        //
+        // - Type for "all" section's account header
+        //   It should have the exact index
+        //   mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount()
+        //
+        // - Type for "loading" view used during "all" section is being loaded.
+        //   It should have the exact index
+        //   mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount() + 1
+        //
+        // As an exception, IGNORE_ITEM_VIEW_TYPE (-1) will be remained as is, which will be used
+        // by framework's Adapter implementation and thus should be left as is.
+        if (position < contactTileAdapterCount) {  // For "tile" and "frequent" sections
             return mContactTileAdapter.getItemViewType(position);
-        } else if (position == contactTileAdapterCount) {
+        } else if (position == contactTileAdapterCount) {  // For "all" section's account header
             return mContactTileAdapter.getViewTypeCount()
                     + mContactEntryListAdapter.getViewTypeCount();
-        } else {
-            final int localPosition = position - contactTileAdapterCount - 1;
-            final int type = mContactEntryListAdapter.getItemViewType(localPosition);
-            // IGNORE_ITEM_VIEW_TYPE must be handled differently.
-            return (type < 0) ? type : type + mContactTileAdapter.getViewTypeCount();
+        } else {  // For "all" section
+            if (mContactEntryListAdapter.isLoading()) {  // "All" section is being loaded.
+                return mContactTileAdapter.getViewTypeCount()
+                        + mContactEntryListAdapter.getViewTypeCount() + 1;
+            } else {
+                // "-1" for mAccountFilterHeaderContainer
+                final int localPosition = position - contactTileAdapterCount - 1;
+                final int type = mContactEntryListAdapter.getItemViewType(localPosition);
+                // IGNORE_ITEM_VIEW_TYPE must be handled differently.
+                return (type < 0) ? type : type + mContactTileAdapter.getViewTypeCount();
+            }
         }
     }
 
@@ -134,7 +184,7 @@
 
         // Obtain a View relevant for that position, and adjust its horizontal padding. Each
         // View has different implementation, so we use different way to control those padding.
-        if (position < contactTileAdapterCount) {
+        if (position < contactTileAdapterCount) {  // For "tile" and "frequent" sections
             final View view = mContactTileAdapter.getView(position, convertView, parent);
             final int frequentHeaderPosition = mContactTileAdapter.getFrequentHeaderPosition();
             if (position < frequentHeaderPosition) {  // "starred" contacts
@@ -153,26 +203,39 @@
                 child.setLayoutParams(params);
             }
             return view;
-        } else if (position == contactTileAdapterCount) {
+        } else if (position == contactTileAdapterCount) {  // For "all" section's account header
             mAccountFilterHeaderContainer.setPadding(mItemPaddingLeft,
                     mAccountFilterHeaderContainer.getPaddingTop(),
                     mItemPaddingRight,
                     mAccountFilterHeaderContainer.getPaddingBottom());
             return mAccountFilterHeaderContainer;
-        } else {
-            final int localPosition = position - contactTileAdapterCount - 1;
-            final ContactListItemView itemView = (ContactListItemView)
-                    mContactEntryListAdapter.getView(localPosition, convertView, null);
-            itemView.setPadding(mItemPaddingLeft, itemView.getPaddingTop(),
-                    mItemPaddingRight, itemView.getPaddingBottom());
-            itemView.setSelectionBoundsHorizontalMargin(mItemPaddingLeft, mItemPaddingRight);
-            return itemView;
+        } else {  // For "all" section
+            if (mContactEntryListAdapter.isLoading()) {  // "All" section is being loaded.
+                mLoadingView.setPadding(mItemPaddingLeft,
+                        mLoadingView.getPaddingTop(),
+                        mItemPaddingRight,
+                        mLoadingView.getPaddingBottom());
+                return mLoadingView;
+            } else {
+                // "-1" for mAccountFilterHeaderContainer
+                final int localPosition = position - contactTileAdapterCount - 1;
+                final ContactListItemView itemView = (ContactListItemView)
+                        mContactEntryListAdapter.getView(localPosition, convertView, null);
+                itemView.setPadding(mItemPaddingLeft, itemView.getPaddingTop(),
+                        mItemPaddingRight, itemView.getPaddingBottom());
+                itemView.setSelectionBoundsHorizontalMargin(mItemPaddingLeft, mItemPaddingRight);
+                return itemView;
+            }
         }
     }
 
     @Override
     public boolean areAllItemsEnabled() {
-        return (mContactTileAdapter.areAllItemsEnabled()
+        // If "all" section is being loaded we'll show mLoadingView, which is not enabled.
+        // Otherwise check the all the other components in the ListView and return appropriate
+        // result.
+        return !mContactEntryListAdapter.isLoading()
+                && (mContactTileAdapter.areAllItemsEnabled()
                 && mAccountFilterHeaderContainer.isEnabled()
                 && mContactEntryListAdapter.areAllItemsEnabled());
     }
@@ -181,14 +244,19 @@
     public boolean isEnabled(int position) {
         final int contactTileAdapterCount = mContactTileAdapter.getCount();
         final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
-        if (position < contactTileAdapterCount) {
+        if (position < contactTileAdapterCount) {  // For "tile" and "frequent" sections
             return mContactTileAdapter.isEnabled(position);
-        } else if (position == contactTileAdapterCount) {
+        } else if (position == contactTileAdapterCount) {  // For "all" section's account header
             // This will be handled by View's onClick event instead of ListView's onItemClick event.
             return false;
-        } else {
-            final int localPosition = position - contactTileAdapterCount - 1;
-            return mContactEntryListAdapter.isEnabled(localPosition);
+        } else {  // For "all" section
+            if (mContactEntryListAdapter.isLoading()) {  // "All" section is being loaded.
+                return false;
+            } else {
+                // "-1" for mAccountFilterHeaderContainer
+                final int localPosition = position - contactTileAdapterCount - 1;
+                return mContactEntryListAdapter.isEnabled(localPosition);
+            }
         }
     }
 
@@ -205,6 +273,7 @@
         if (position <= contactTileAdapterCount) {
             return 0;
         } else {
+            // "-1" for mAccountFilterHeaderContainer
             final int localPosition = position - contactTileAdapterCount - 1;
             return mContactEntryListAdapter.getSectionForPosition(localPosition);
         }
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
index 6886e81..9be53f6 100644
--- a/src/com/android/contacts/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -114,7 +114,7 @@
             return;
         }
         final boolean shouldShowHeader = AccountFilterUtil.updateAccountFilterTitleForPhone(
-                mAccountFilterHeader, filter, false, false);
+                mAccountFilterHeader, filter, false);
         if (shouldShowHeader) {
             mPaddingView.setVisibility(View.GONE);
             mAccountFilterHeader.setVisibility(View.VISIBLE);
diff --git a/src/com/android/contacts/util/AccountFilterUtil.java b/src/com/android/contacts/util/AccountFilterUtil.java
index d63db55..3f055d0 100644
--- a/src/com/android/contacts/util/AccountFilterUtil.java
+++ b/src/com/android/contacts/util/AccountFilterUtil.java
@@ -44,9 +44,8 @@
      * showing or hiding this entire view.
      */
     public static boolean updateAccountFilterTitleForPeople(View filterContainer,
-            ContactListFilter filter, boolean isLoading, boolean showTitleForAllAccounts) {
-        return updateAccountFilterTitle(
-                filterContainer, filter, isLoading, showTitleForAllAccounts, false);
+            ContactListFilter filter, boolean showTitleForAllAccounts) {
+        return updateAccountFilterTitle(filterContainer, filter, showTitleForAllAccounts, false);
     }
 
     /**
@@ -54,22 +53,20 @@
      * boolean)}, but for Phone UI.
      */
     public static boolean updateAccountFilterTitleForPhone(View filterContainer,
-            ContactListFilter filter, boolean isLoading, boolean showTitleForAllAccounts) {
+            ContactListFilter filter, boolean showTitleForAllAccounts) {
         return updateAccountFilterTitle(
-                filterContainer, filter, isLoading, showTitleForAllAccounts, true);
+                filterContainer, filter, showTitleForAllAccounts, true);
     }
 
     private static boolean updateAccountFilterTitle(View filterContainer,
-            ContactListFilter filter, boolean isLoading, boolean showTitleForAllAccounts,
+            ContactListFilter filter, boolean showTitleForAllAccounts,
             boolean forPhone) {
         final Context context = filterContainer.getContext();
         final TextView headerTextView = (TextView)
                 filterContainer.findViewById(R.id.account_filter_header);
 
         boolean textWasSet = false;
-        if (isLoading) {
-            headerTextView.setText(R.string.contact_list_loading);
-        } else if (filter != null) {
+        if (filter != null) {
             if (forPhone) {
                 if (filter.filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) {
                     if (showTitleForAllAccounts) {