clean up attachment loaders

Need to clean up upon either view recycle or full-on view
detach. Also make sure to finish removing scrap views when
the conversation view is torn down, like AbsListView does.

Change-Id: Icee78edecaec757d66e91b9a692548375ad65227
diff --git a/src/com/android/mail/browse/MessageHeaderView.java b/src/com/android/mail/browse/MessageHeaderView.java
index ba5d551..f1077bf 100644
--- a/src/com/android/mail/browse/MessageHeaderView.java
+++ b/src/com/android/mail/browse/MessageHeaderView.java
@@ -56,6 +56,7 @@
 import com.android.mail.providers.Attachment;
 import com.android.mail.providers.Message;
 import com.android.mail.providers.UIProvider;
+import com.android.mail.ui.ConversationContainer;
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
 
@@ -63,7 +64,8 @@
 import java.io.StringReader;
 
 public class MessageHeaderView extends LinearLayout implements OnClickListener,
-        OnMenuItemClickListener, HeaderBlock, LoaderManager.LoaderCallbacks<Cursor> {
+        OnMenuItemClickListener, HeaderBlock, LoaderManager.LoaderCallbacks<Cursor>,
+        ConversationContainer.DetachListener {
 
     /**
      * Cap very long recipient lists during summary construction for efficiency.
@@ -335,6 +337,14 @@
         mDefaultReplyAll = defaultReplyAll;
     }
 
+    private Integer getLoaderId() {
+        Integer id = null;
+        if (mMessage != null && mMessage.uri != null) {
+            id = mMessage.uri.hashCode();
+        }
+        return id;
+    }
+
     public int bind(Message message) {
         Timer t = new Timer();
         t.start(HEADER_RENDER_TAG);
@@ -349,8 +359,6 @@
             mCallbacks.onHeaderCreated(mLocalMessageId);
         }
 
-        setTag(mLocalMessageId);
-
         mTimestampMs = mMessage.dateReceivedMs;
         if (mDateBuilder != null) {
             mTimestampShort = mDateBuilder.formatShortDate(mTimestampMs);
@@ -367,7 +375,8 @@
 
         // kick off load of Attachment objects in background thread
         if (mMessage.hasAttachments) {
-            mLoaderManager.initLoader(mMessage.hashCode(), Bundle.EMPTY, this);
+            LogUtils.d(LOG_TAG, "calling initLoader for message %d", getLoaderId());
+            mLoaderManager.initLoader(getLoaderId(), Bundle.EMPTY, this);
             // TODO: clean up loader when the view is detached
         }
 
@@ -449,7 +458,27 @@
 
     @Override
     public void onLoaderReset(Loader<Cursor> loader) {
-        // Do nothing.
+        mAttachments = null;
+    }
+
+    private void destroyLoader() {
+        final Integer loaderId = getLoaderId();
+        if (mLoaderManager != null && loaderId != null) {
+            LogUtils.d(LOG_TAG, "detaching header view, calling destroyLoader for message %d",
+                    loaderId);
+            mLoaderManager.destroyLoader(loaderId);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        destroyLoader();
+    }
+
+    @Override
+    public void onDetachedFromParent() {
+        destroyLoader();
     }
 
     private boolean isInOutbox() {
diff --git a/src/com/android/mail/ui/ConversationContainer.java b/src/com/android/mail/ui/ConversationContainer.java
index c229c29..24e04a6 100644
--- a/src/com/android/mail/ui/ConversationContainer.java
+++ b/src/com/android/mail/ui/ConversationContainer.java
@@ -123,6 +123,19 @@
 
     private static final int VIEW_TAG_CONVERSATION_INDEX = R.id.view_tag_conversation_index;
 
+    /**
+     * Child views of this container should implement this interface to be notified when they are
+     * being detached.
+     *
+     */
+    public interface DetachListener {
+        /**
+         * Called on a child view when it is removed from its parent as part of
+         * {@link ConversationContainer} view recycling.
+         */
+        void onDetachedFromParent();
+    }
+
     public ConversationContainer(Context c) {
         this(c, null);
     }
@@ -333,6 +346,19 @@
         detachViewFromParent(overlayView);
         mScrapViews.add(overlayView);
         mChildrenToRemove.remove(overlayView);
+        if (overlayView instanceof DetachListener) {
+            ((DetachListener) overlayView).onDetachedFromParent();
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        for (View scrap : mScrapViews) {
+            removeDetachedView(scrap, false /* animate */);
+        }
+        mScrapViews.clear();
     }
 
     @Override