Merge "Import translations. DO NOT MERGE" into jb-ub-mail
diff --git a/assets/script.js b/assets/script.js
index 7efdc31..ee15d03 100644
--- a/assets/script.js
+++ b/assets/script.js
@@ -105,14 +105,12 @@
  */
 function rewriteRelativeImageSrc(imgElement) {
     var src = imgElement.src;
-    if (src.indexOf(ACCOUNT_URI) == 0) {
-        var questionPos = src.indexOf('?');
-        if (ACCOUNT_URI.indexOf('content://') == 0 && questionPos != -1) {
-            // For some reason, when webview makes a content provider openFile call the query
-            // parameters are removed.  Instead, replace the '?' with '/'
-            src = src.substring(0, questionPos) + "/" + src.substring(questionPos + 1);
-            imgElement.src = src;
-        }
+
+    // DOC_BASE_URI will always be a unique x-thread:// uri for this particular conversation
+    if (src.indexOf(DOC_BASE_URI) == 0 && (DOC_BASE_URI != CONVERSATION_BASE_URI)) {
+        // The conversation specifies a different base uri than the document
+        src = CONVERSATION_BASE_URI + src.substring(DOC_BASE_URI.length);
+        imgElement.src = src;
     }
 };
 
diff --git a/res/raw/template_conversation_lower.html b/res/raw/template_conversation_lower.html
index 9f69deb..7523981 100644
--- a/res/raw/template_conversation_lower.html
+++ b/res/raw/template_conversation_lower.html
@@ -2,7 +2,8 @@
 <script type="text/javascript">
   var MSG_HIDE_ELIDED = '%s';
   var MSG_SHOW_ELIDED = '%s';
-  var ACCOUNT_URI = '%s';
+  var DOC_BASE_URI = '%s';
+  var CONVERSATION_BASE_URI = '%s';
   var VIEW_WIDTH = %d;
   var WIDE_VIEWPORT_WIDTH = %d;
 </script>
diff --git a/src/com/android/mail/browse/ConversationItemViewCoordinates.java b/src/com/android/mail/browse/ConversationItemViewCoordinates.java
index 95cdfaf..0a35b88 100644
--- a/src/com/android/mail/browse/ConversationItemViewCoordinates.java
+++ b/src/com/android/mail/browse/ConversationItemViewCoordinates.java
@@ -439,7 +439,7 @@
     public static int getMinHeight(Context context, ViewMode viewMode) {
         int mode = ConversationItemViewCoordinates.getMode(context, viewMode);
         return context.getResources().getDimensionPixelSize(
-                mode == WIDE_MODE ? R.dimen.conversation_item_height
-                        : R.dimen.conversation_item_height_wide);
+                mode == WIDE_MODE ?
+                        R.dimen.conversation_item_height_wide : R.dimen.conversation_item_height);
     }
 }
diff --git a/src/com/android/mail/browse/ConversationItemViewModel.java b/src/com/android/mail/browse/ConversationItemViewModel.java
index 6508ea4..267900e 100644
--- a/src/com/android/mail/browse/ConversationItemViewModel.java
+++ b/src/com/android/mail/browse/ConversationItemViewModel.java
@@ -234,8 +234,7 @@
      * Returns the layout hashcode to compare to see if the layout state has changed.
      */
     private int getLayoutHashCode() {
-        return mDataHashCode ^ viewWidth ^ standardScaledDimen
-                ^ Boolean.valueOf(checkboxVisible).hashCode();
+        return Objects.hashCode(mDataHashCode, viewWidth, standardScaledDimen, checkboxVisible);
     }
 
     private Object getConvInfo() {
diff --git a/src/com/android/mail/compose/AttachmentsView.java b/src/com/android/mail/compose/AttachmentsView.java
index b068cd3..c566214 100644
--- a/src/com/android/mail/compose/AttachmentsView.java
+++ b/src/com/android/mail/compose/AttachmentsView.java
@@ -15,6 +15,8 @@
  */
 package com.android.mail.compose;
 
+import android.animation.LayoutTransition;
+import android.animation.LayoutTransition.TransitionListener;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
@@ -52,7 +54,7 @@
 /*
  * View for displaying attachments in the compose screen.
  */
-class AttachmentsView extends LinearLayout implements OnClickListener {
+class AttachmentsView extends LinearLayout implements OnClickListener, TransitionListener {
     private static final String LOG_TAG = LogTag.getLogTag();
 
     private final Resources mResources;
@@ -64,8 +66,10 @@
     private GridLayout mCollapseLayout;
     private TextView mCollapseText;
     private ImageView mCollapseCaret;
+    private LayoutTransition mComposeLayoutTransition;
 
     private boolean mIsExpanded;
+    private long mChangingDelay;
 
     public AttachmentsView(Context context) {
         this(context, null);
@@ -96,9 +100,13 @@
             case R.id.attachment_collapse_view:
                 if (mIsExpanded) {
                     collapseView();
+                    mComposeLayoutTransition.setStartDelay(
+                            LayoutTransition.CHANGING, mChangingDelay);
                 } else {
                     expandView();
+                    mComposeLayoutTransition.setStartDelay(LayoutTransition.CHANGING, 0l);
                 }
+                mComposeLayoutTransition.enableTransitionType(LayoutTransition.CHANGING);
                 break;
         }
     }
@@ -197,6 +205,14 @@
     @VisibleForTesting
     protected void deleteAttachment(final View attachmentView,
             final Attachment attachment) {
+        mComposeLayoutTransition.enableTransitionType(LayoutTransition.CHANGING);
+        mComposeLayoutTransition.setStartDelay(
+                LayoutTransition.CHANGING, mChangingDelay);
+        final LayoutTransition transition = getLayoutTransition();
+        transition.enableTransitionType(LayoutTransition.CHANGING);
+        transition.setStartDelay(LayoutTransition.CHANGING, mChangingDelay);
+        transition.addTransitionListener(this);
+
         mAttachments.remove(attachment);
         ((ViewGroup) attachmentView.getParent()).removeView(attachmentView);
         if (mChangeListener != null) {
@@ -210,6 +226,26 @@
         }
     }
 
+    public void setComposeLayoutTransition(LayoutTransition transition) {
+        mComposeLayoutTransition = transition;
+        mComposeLayoutTransition.addTransitionListener(this);
+        mChangingDelay =
+                mComposeLayoutTransition.getDuration(LayoutTransition.DISAPPEARING);
+    }
+
+    @Override
+    public void startTransition(LayoutTransition transition, ViewGroup container, View view,
+            int transitionType) {
+        /* Do nothing */
+    }
+
+    @Override
+    public void endTransition(LayoutTransition transition, ViewGroup container, View view,
+            int transitionType) {
+        transition.disableTransitionType(LayoutTransition.CHANGING);
+        transition.setStartDelay(LayoutTransition.CHANGING, 0l);
+    }
+
     /**
      * Get all attachments being managed by this view.
      * @return attachments.
diff --git a/src/com/android/mail/compose/ComposeActivity.java b/src/com/android/mail/compose/ComposeActivity.java
index addc645..766aa6f 100644
--- a/src/com/android/mail/compose/ComposeActivity.java
+++ b/src/com/android/mail/compose/ComposeActivity.java
@@ -799,18 +799,15 @@
     }
 
     private void findViews() {
-        LayoutTransition transition =
-                ((ViewGroup) findViewById(R.id.content)).getLayoutTransition();
-        transition.enableTransitionType(LayoutTransition.CHANGING);
-        long delay = transition.getDuration(LayoutTransition.DISAPPEARING);
-        transition.setStartDelay(LayoutTransition.CHANGING, delay);
-
         mCcBccButton = (Button) findViewById(R.id.add_cc_bcc);
         if (mCcBccButton != null) {
             mCcBccButton.setOnClickListener(this);
         }
         mCcBccView = (CcBccView) findViewById(R.id.cc_bcc_wrapper);
         mAttachmentsView = (AttachmentsView)findViewById(R.id.attachments);
+        LayoutTransition transition =
+                ((ViewGroup) findViewById(R.id.content)).getLayoutTransition();
+        mAttachmentsView.setComposeLayoutTransition(transition);
         mAttachmentsButton = (ImageView) findViewById(R.id.add_attachment);
         if (mAttachmentsButton != null) {
             mAttachmentsButton.setOnClickListener(this);
diff --git a/src/com/android/mail/providers/Conversation.java b/src/com/android/mail/providers/Conversation.java
index 8a06a48..ccb36ea 100644
--- a/src/com/android/mail/providers/Conversation.java
+++ b/src/com/android/mail/providers/Conversation.java
@@ -130,10 +130,14 @@
      */
     public ConversationInfo conversationInfo;
     /**
-     * @see UIProvider.ConversationColumns#CONVERSATION_INFO
+     * @see UIProvider.ConversationColumns#CONVERSATION_BASE_URI
      */
     public Uri conversationBaseUri;
     /**
+     * @see UIProvider.ConversationColumns#CONVERSATION_COOKIE
+     */
+    public String conversationCookie;
+    /**
      * @see UIProvider.ConversationColumns#REMOTE
      */
     public boolean isRemote;
@@ -189,6 +193,7 @@
         dest.writeParcelable(accountUri, 0);
         dest.writeString(ConversationInfo.toString(conversationInfo));
         dest.writeParcelable(conversationBaseUri, 0);
+        dest.writeString(conversationCookie);
         dest.writeInt(isRemote ? 1 : 0);
     }
 
@@ -219,6 +224,7 @@
         localDeleteOnUpdate = false;
         conversationInfo = ConversationInfo.fromString(in.readString());
         conversationBaseUri = in.readParcelable(null);
+        conversationCookie = in.readString();
         isRemote = in.readInt() != 0;
     }
 
@@ -283,6 +289,7 @@
                     cursor.getString(UIProvider.CONVERSATION_BASE_URI_COLUMN);
             conversationBaseUri = !TextUtils.isEmpty(conversationBase) ?
                     Uri.parse(conversationBase) : null;
+            conversationCookie = cursor.getString(UIProvider.CONVERSATION_COOKIE_COLUMN);
             if (conversationInfo == null) {
                 snippet = cursor.getString(UIProvider.CONVERSATION_SNIPPET_COLUMN);
                 senders = emptyIfNull(cursor.getString(UIProvider.CONVERSATION_SENDER_INFO_COLUMN));
@@ -301,7 +308,7 @@
             int numMessages, int numDrafts, int sendingState, int priority, boolean read,
             boolean starred, String rawFolders, int convFlags, int personalLevel, boolean spam,
             boolean phishing, boolean muted, Uri accountUri, ConversationInfo conversationInfo,
-            Uri conversationBase, boolean isRemote) {
+            Uri conversationBase, String conversationCookie, boolean isRemote) {
 
         final Conversation conversation = new Conversation();
 
@@ -329,6 +336,7 @@
         conversation.accountUri = accountUri;
         conversation.conversationInfo = conversationInfo;
         conversation.conversationBaseUri = conversationBase;
+        conversation.conversationCookie = conversationCookie;
         conversation.isRemote = isRemote;
         return conversation;
     }
diff --git a/src/com/android/mail/providers/UIProvider.java b/src/com/android/mail/providers/UIProvider.java
index 630e663..af2db9e 100644
--- a/src/com/android/mail/providers/UIProvider.java
+++ b/src/com/android/mail/providers/UIProvider.java
@@ -770,6 +770,7 @@
         ConversationColumns.ACCOUNT_URI,
         ConversationColumns.SENDER_INFO,
         ConversationColumns.CONVERSATION_BASE_URI,
+        ConversationColumns.CONVERSATION_COOKIE,
         ConversationColumns.REMOTE
     };
 
@@ -799,7 +800,8 @@
     public static final int CONVERSATION_ACCOUNT_URI_COLUMN = 21;
     public static final int CONVERSATION_SENDER_INFO_COLUMN = 22;
     public static final int CONVERSATION_BASE_URI_COLUMN = 23;
-    public static final int CONVERSATION_REMOTE_COLUMN = 24;
+    public static final int CONVERSATION_COOKIE_COLUMN = 24;
+    public static final int CONVERSATION_REMOTE_COLUMN = 25;
 
     public static final class ConversationSendingState {
         public static final int OTHER = 0;
@@ -961,12 +963,17 @@
          * {@link Conversation} object.
          */
         public static final String VIEWED = "viewed";
-
         /**
          * This String column contains the base uri for this conversation.  This uri can be used
          * when handling relative urls in the message content
          */
         public static final String CONVERSATION_BASE_URI = "conversationBaseUri";
+        /**
+         * This String column contains the cookie needed for accessing inline content.  The cookie
+         * specified here will be set on the uri specified in the {@link CONVERSATION_BASE_URI}
+         * column.
+         */
+        public static final String CONVERSATION_COOKIE = "conversationCookie";
 
         private ConversationColumns() {
         }
diff --git a/src/com/android/mail/ui/AnimatedAdapter.java b/src/com/android/mail/ui/AnimatedAdapter.java
index 1728ed7..d33be03 100644
--- a/src/com/android/mail/ui/AnimatedAdapter.java
+++ b/src/com/android/mail/ui/AnimatedAdapter.java
@@ -271,19 +271,10 @@
                 return v;
             }
         }
-        // TODO: do this in the swipe helper?
-        // If this view gets recycled, we need to reset things set by the
-        // animation.
-        if (convertView != null) {
-            if (convertView.getAlpha() < 1) {
-                convertView.setAlpha(1);
-            }
-            if (convertView.getTranslationX() != 0) {
-                convertView.setTranslationX(0);
-            }
-            if (convertView instanceof SwipeableConversationItemView) {
-                ((SwipeableConversationItemView)convertView).reset();
-            }
+        if (!(convertView instanceof SwipeableConversationItemView)) {
+            convertView = null;
+        } else if (convertView != null) {
+            ((SwipeableConversationItemView) convertView).reset();
         }
         View v = super.getView(position, convertView, parent);
         if (v == null) {
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index 87b6826..d656d08 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -29,6 +29,7 @@
 import android.database.DataSetObservable;
 import android.database.DataSetObserver;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.provider.Browser;
@@ -40,6 +41,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.webkit.ConsoleMessage;
+import android.webkit.CookieManager;
+import android.webkit.CookieSyncManager;
 import android.webkit.WebChromeClient;
 import android.webkit.WebSettings;
 import android.webkit.WebView;
@@ -240,6 +243,13 @@
         mWebView.setOnCreateContextMenuListener(new WebViewContextMenu(activity));
 
         showConversation();
+
+        if (mConversation.conversationBaseUri != null &&
+                !TextUtils.isEmpty(mConversation.conversationCookie)) {
+            // Set the cookie for this base url
+            new SetCookieTask(mConversation.conversationBaseUri.toString(),
+                    mConversation.conversationCookie).execute();
+        }
     }
 
     @Override
@@ -251,10 +261,9 @@
         mAccount = args.getParcelable(ARG_ACCOUNT);
         mConversation = args.getParcelable(ARG_CONVERSATION);
         mFolder = args.getParcelable(ARG_FOLDER);
-        // If the provider has specified a base uri to be used, use that one.
-        mBaseUri = mConversation.conversationBaseUri != null ?
-                mConversation.conversationBaseUri.toString() :
-                "x-thread://" + mAccount.name + "/" + mConversation.id;
+        // Since the uri specified in the conversation base uri may not be unique, we specify a
+        // base uri that us guaranteed to be unique for this conversation.
+        mBaseUri = "x-thread://" + mAccount.name + "/" + mConversation.id;
 
         // Not really, we just want to get a crack to store a reference to the change_folder item
         setHasOptionsMenu(true);
@@ -600,7 +609,11 @@
 
         mWebView.getSettings().setBlockNetworkImage(!allowNetworkImages);
 
-        return mTemplates.endConversation(mBaseUri, 320, mWebView.getViewportWidth());
+        // If the conversation has specified a base uri, use it here, use mBaseUri
+        final String conversationBaseUri = mConversation.conversationBaseUri != null ?
+                mConversation.conversationBaseUri.toString() : mBaseUri;
+        return mTemplates.endConversation(mBaseUri, conversationBaseUri, 320,
+                mWebView.getViewportWidth());
     }
 
     private void renderSuperCollapsedBlock(int start, int end) {
@@ -1172,4 +1185,22 @@
         return mAccount.settings;
     }
 
+    private class SetCookieTask extends AsyncTask<Void, Void, Void> {
+        final String mUri;
+        final String mCookie;
+
+        SetCookieTask(String uri, String cookie) {
+            mUri = uri;
+            mCookie = cookie;
+        }
+
+        @Override
+        public Void doInBackground(Void... args) {
+            final CookieSyncManager csm =
+                CookieSyncManager.createInstance(mContext);
+            CookieManager.getInstance().setCookie(mUri, mCookie);
+            csm.sync();
+            return null;
+        }
+    }
 }
diff --git a/src/com/android/mail/ui/HtmlConversationTemplates.java b/src/com/android/mail/ui/HtmlConversationTemplates.java
index 002bed4..16b26b7 100644
--- a/src/com/android/mail/ui/HtmlConversationTemplates.java
+++ b/src/com/android/mail/ui/HtmlConversationTemplates.java
@@ -187,13 +187,15 @@
         mInProgress = true;
     }
 
-    public String endConversation(String baseUri, int viewWidth, int viewportWidth) {
+    public String endConversation(String docBaseUri, String conversationBaseUri, int viewWidth,
+            int viewportWidth) {
         if (!mInProgress) {
             throw new IllegalStateException("must call startConversation first");
         }
 
         append(sConversationLower, mContext.getString(R.string.hide_elided),
-                mContext.getString(R.string.show_elided), baseUri, viewWidth, viewportWidth);
+                mContext.getString(R.string.show_elided), docBaseUri, conversationBaseUri,
+                viewWidth, viewportWidth);
 
         mInProgress = false;
 
diff --git a/src/com/android/mail/ui/SwipeableListView.java b/src/com/android/mail/ui/SwipeableListView.java
index 233726c..985f38c 100644
--- a/src/com/android/mail/ui/SwipeableListView.java
+++ b/src/com/android/mail/ui/SwipeableListView.java
@@ -17,8 +17,6 @@
 
 package com.android.mail.ui;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.net.Uri;
@@ -320,7 +318,8 @@
             // We made it up to the window without find this list view
             return INVALID_POSITION;
         } catch (NullPointerException e) {
-            LogUtils.e(LOG_TAG, e, "WHAT HAS NO PARENT " + (v != null ? v.getClass() : null));
+            LogUtils.e(LOG_TAG, e, "WHAT HAS NO PARENT "
+                    + (listItem != null ? listItem.getClass() : null));
             return INVALID_POSITION;
         }
         return super.getPositionForView(view);
@@ -329,26 +328,23 @@
     /**
      * Archive items using the swipe away animation before shrinking them away.
      */
-    public void destroyItems(ArrayList<ConversationItemView> views,
+    public void destroyItems(final ArrayList<ConversationItemView> views,
             final DestructiveAction listener) {
         if (views == null || views.size() == 0) {
             return;
         }
+        // Need to find the items in the LIST!
         final ArrayList<Conversation> conversations = new ArrayList<Conversation>();
         for (ConversationItemView view : views) {
             Conversation conv = view.getConversation();
-            conv.position = view.getParent() != null ? getPositionForView(view) : -1;
+            conv.position = conv.position == -1 && view.getParent() != null ?
+                    getPositionForView(view) : conv.position;
             conversations.add(conv);
         }
-        mSwipeHelper.dismissChildren(views.get(0), views, new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                AnimatedAdapter adapter = getAnimatedAdapter();
-                if (adapter != null) {
-                    adapter.delete(conversations, listener);
-                }
-            }
-        });
+        AnimatedAdapter adapter = getAnimatedAdapter();
+        if (adapter != null) {
+            adapter.delete(conversations, listener);
+        }
     }
 
     private AnimatedAdapter getAnimatedAdapter() {