Setup the loading footer for search results and other folders.

shows sync status or errors.
This will eventually be where "load more" lives
Currently only shows for search results
Can easily be updated to show for all conv lists.
Change-Id: I87ea19ebca13617f8a0ba7aed4f63f49b2b69c14
diff --git a/res/layout/conversation_list_footer_view.xml b/res/layout/conversation_list_footer_view.xml
new file mode 100644
index 0000000..1b236c3
--- /dev/null
+++ b/res/layout/conversation_list_footer_view.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2007 Google Inc.
+ *
+ * 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.
+ */
+-->
+
+<com.android.mail.browse.ConversationListFooterView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout android:id="@+id/network_error"
+        android:layout_width="match_parent"
+        android:layout_height="?android:attr/listPreferredItemHeight"
+        android:padding="5dip"
+        android:visibility="gone"
+        android:gravity="center_vertical">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/ic_email_network_error" />
+
+        <TextView android:id="@+id/error_text"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:textAppearance="?android:attr/textAppearanceLargeInverse"
+            android:layout_marginBottom="1dip"
+            android:paddingLeft="5dip"
+            android:textColor="@android:color/black"
+            android:text="@string/network_error"/>
+
+        <Button android:id="@+id/retry_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="?android:attr/buttonStyleSmall"
+            android:textColor="@android:color/black"
+            android:text="@string/retry"
+            android:paddingTop="15dip"
+            android:paddingBottom="15dip"
+            android:paddingLeft="20dip"
+            android:paddingRight="20dip"/>
+
+    </LinearLayout>
+
+    <LinearLayout android:id="@+id/loading"
+        android:layout_width="match_parent"
+        android:layout_height="?android:attr/listPreferredItemHeight"
+        android:gravity="center"
+        android:visibility="gone">
+
+        <ProgressBar
+            style="?android:attr/progressBarStyle"
+            android:indeterminate="true"
+            android:layout_width="32dp"
+            android:layout_height="32dp" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceLargeInverse"
+            android:layout_marginBottom="1dip"
+            android:paddingLeft="5dip"
+            android:textColor="@android:color/black"
+            android:text="@string/loading"/>
+
+    </LinearLayout>
+
+</com.android.mail.browse.ConversationListFooterView>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1a926c7..037ac08 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -559,4 +559,11 @@
 
     <!-- Show in the actionbar -->
     <string name="labels">Labels</string>
+
+    <!-- Shown in conversation list footer when application cannot make a connection [CHAR LIMIT=20]-->
+    <string name="network_error">No connection</string>
+        <!-- Message at the bottom of the conversation list screen if all conversations haven't been loaded yet [CHAR LIMIT=20]-->
+    <string name="loading">Loading conversations\u2026</string>
+    <!-- Button at bottom of conversation list screen if last attempt to load conversations failed [CHAR LIMIT=20]-->
+    <string name="retry">Retry</string>
 </resources>
diff --git a/src/com/android/mail/browse/ConversationListFooterView.java b/src/com/android/mail/browse/ConversationListFooterView.java
new file mode 100644
index 0000000..ac89152
--- /dev/null
+++ b/src/com/android/mail/browse/ConversationListFooterView.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ * Licensed to 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.mail.browse;
+
+import android.content.Context;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.mail.R;
+import com.android.mail.providers.Folder;
+import com.android.mail.providers.UIProvider;
+import com.android.mail.providers.UIProvider.LastSyncResult;
+import com.android.mail.utils.Utils;
+
+public class ConversationListFooterView extends LinearLayout implements View.OnClickListener {
+    private View mLoading;
+    private View mNetworkError;
+    private View mRetry;
+    private TextView mErrorText;
+
+    public ConversationListFooterView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mLoading = findViewById(R.id.loading);
+        mNetworkError = findViewById(R.id.network_error);
+        mRetry = findViewById(R.id.retry_button);
+        mRetry.setOnClickListener(this);
+        mErrorText = (TextView)findViewById(R.id.error_text);
+    }
+
+    public void onClick(View v) {
+        Folder f = (Folder)v.getTag();
+        if (f != null) {
+            // TODO(mindyp) Refresh.
+        }
+    }
+
+    /**
+     * Update the view to reflect the new folder status.
+     */
+    public void updateStatus(final Folder folder) {
+        mRetry.setTag(folder);
+
+        if (folder.isSyncInProgress()) {
+            mLoading.setVisibility(View.VISIBLE);
+            mNetworkError.setVisibility(View.GONE);
+        } else if (folder.lastSyncResult != UIProvider.LastSyncResult.SUCCESS) {
+            mNetworkError.setVisibility(View.VISIBLE);
+            CharSequence error = Utils.getSyncStatusText(getContext(), folder.lastSyncResult);
+            if (!TextUtils.isEmpty(error)) {
+                mErrorText.setText(error);
+            }
+            mLoading.setVisibility(View.GONE);
+            // Only show the "Retry" button for I/O errors; it won't help for
+            // internal errors.
+            mRetry.setVisibility(
+                    folder.lastSyncResult == UIProvider.LastSyncResult.CONNECTION_ERROR ?
+                    View.VISIBLE : View.GONE);
+        }
+    }
+}
diff --git a/src/com/android/mail/ui/AnimatedAdapter.java b/src/com/android/mail/ui/AnimatedAdapter.java
index ec6c7fb..b483be7 100644
--- a/src/com/android/mail/ui/AnimatedAdapter.java
+++ b/src/com/android/mail/ui/AnimatedAdapter.java
@@ -28,6 +28,7 @@
 
 import com.android.mail.browse.ConversationCursor;
 import com.android.mail.browse.ConversationItemView;
+import com.android.mail.browse.ConversationListFooterView;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.UIProvider;
@@ -39,6 +40,7 @@
 
 public class AnimatedAdapter extends SimpleCursorAdapter implements
         android.animation.Animator.AnimatorListener, OnUndoCancelListener {
+    private static int ITEM_VIEW_TYPE_FOOTER = 1;
     private HashSet<Integer> mDeletingItems = new HashSet<Integer>();
     private Account mSelectedAccount;
     private Context mContext;
@@ -47,6 +49,8 @@
     private boolean mUndo = false;
     private ArrayList<Integer> mLastDeletingItems = new ArrayList<Integer>();
     private ViewMode mViewMode;
+    private View mFooter;
+    private boolean mShowFooter;
 
     public AnimatedAdapter(Context context, int textViewResourceId, ConversationCursor cursor,
             ConversationSelectionSet batch, Account account, ViewMode viewMode) {
@@ -55,6 +59,13 @@
         mBatchConversations = batch;
         mSelectedAccount = account;
         mViewMode = viewMode;
+        mShowFooter = false;
+    }
+
+    @Override
+    public int getCount() {
+        int count = super.getCount();
+        return mShowFooter? count + 1 : count;
     }
 
     public void setUndo(boolean state) {
@@ -78,7 +89,7 @@
 
     @Override
     public void bindView(View view, Context context, Cursor cursor) {
-        if (!isPositionAnimating(view)) {
+        if (!isPositionAnimating(view) && !isPositionFooter(view)) {
             ((ConversationItemView) view).bind(cursor, mSelectedAccount.name, null,
                     mViewMode, mBatchConversations);
         }
@@ -92,7 +103,7 @@
     @Override
     public int getViewTypeCount() {
         // Our normal view and the animating (not recycled) view
-        return 2;
+        return 3;
     }
 
     @Override
@@ -100,6 +111,8 @@
         // Don't recycle animating views
         if (isPositionAnimating(position)) {
             return AdapterView.ITEM_VIEW_TYPE_IGNORE;
+        } else if (mShowFooter && position == super.getCount()) {
+            return ITEM_VIEW_TYPE_FOOTER ;
         }
         return 0;
     }
@@ -128,6 +141,9 @@
 
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
+        if (mShowFooter && position == super.getCount()) {
+            return mFooter;
+        }
         if (isPositionAnimating(position)) {
             return getAnimatingView(position, convertView, parent);
         }
@@ -160,6 +176,10 @@
         return (view instanceof AnimatingItemView);
     }
 
+    private boolean isPositionFooter(View view) {
+        return (view instanceof ConversationListFooterView);
+    }
+
     @Override
     public void onAnimationStart(Animator animation) {
         // TODO Auto-generated method stub
@@ -200,4 +220,22 @@
     public void onUndoCancel() {
         mLastDeletingItems.clear();
     }
+
+    public void showFooter() {
+        if (!mShowFooter) {
+            mShowFooter = true;
+            notifyDataSetChanged();
+        }
+    }
+
+    public void hideFooter() {
+        if (mShowFooter) {
+            mShowFooter = false;
+            notifyDataSetChanged();
+        }
+    }
+
+    public void addFooter(View footerView) {
+        mFooter = footerView;
+    }
 }
diff --git a/src/com/android/mail/ui/ConversationListFragment.java b/src/com/android/mail/ui/ConversationListFragment.java
index 0badd7b..61079cc 100644
--- a/src/com/android/mail/ui/ConversationListFragment.java
+++ b/src/com/android/mail/ui/ConversationListFragment.java
@@ -40,6 +40,7 @@
 import com.android.mail.browse.ConversationCursor;
 import com.android.mail.browse.ConversationCursor.ConversationListener;
 import com.android.mail.browse.ConversationItemView;
+import com.android.mail.browse.ConversationListFooterView;
 import com.android.mail.browse.SelectedConversationsActionMenu;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
@@ -126,6 +127,7 @@
      */
     private ConversationSelectionSet mSelectedSet = new ConversationSelectionSet();
     private SelectedConversationsActionMenu mSelectedConversationsActionMenu;
+    private ConversationListFooterView mFooterView;
 
     /**
      * Constructor needs to be public to handle orientation changes and activity lifecycle events.
@@ -221,6 +223,10 @@
 
         mListAdapter = new AnimatedAdapter(mActivity.getApplicationContext(), -1,
                 mConversationListCursor, mSelectedSet, mAccount, mActivity.getViewMode());
+        mFooterView = (ConversationListFooterView) LayoutInflater.from(
+                mActivity.getActivityContext()).inflate(R.layout.conversation_list_footer_view,
+                null);
+        mListAdapter.addFooter(mFooterView);
         mListView.setAdapter(mListAdapter);
         // Don't need to add ourselves to our own set observer.
         // mActivity.getBatchConversations().addObserver(this);
@@ -514,6 +520,7 @@
     @Override
     public Loader<ConversationCursor> onCreateLoader(int id, Bundle args) {
         configureSearchResultHeader();
+        mListAdapter.hideFooter();
         return new ConversationCursorLoader((Activity) mActivity,
                     UIProvider.CONVERSATION_PROJECTION, mFolder.conversationListUri);
     }
@@ -542,10 +549,17 @@
 
     public void onSearchFolderUpdated(Folder folder) {
         mFolder = folder;
-        // Check the status of the folder to see if we are done loading.
-        if (!mFolder.isSyncInProgress()
+        mFooterView.updateStatus(mFolder);
+        if (mFolder.isSyncInProgress()) {
+            mListAdapter.showFooter();
+        } else if (!mFolder.isSyncInProgress()
                 && mFolder.lastSyncResult == UIProvider.LastSyncResult.SUCCESS) {
+            // Check the status of the folder to see if we are done loading.
             updateSearchResultHeader(mFolder != null ? mFolder.totalCount : 0);
+            if (mFolder.totalCount == 0) {
+                mListView.setEmptyView(mEmptyView);
+            }
+            mListAdapter.hideFooter();
         }
     }
 }