Move over attachment views, attachment handling, logs, utils.

Attachment is a first very bare bones attempt at bringing together
the two providers. I only made it to stand in while we work out the attachments
providers interface so I could check the visual.
This uses a Mock protocol attachment.
Change-Id: I4670059a1fb743fa0a93af24226c6dcd1be87273
diff --git a/Android.mk b/Android.mk
index a9a69b7..34d6394 100644
--- a/Android.mk
+++ b/Android.mk
@@ -11,7 +11,8 @@
 src_dirs := src
 LOCAL_PACKAGE_NAME := UnifiedEmail
 
-LOCAL_STATIC_JAVA_LIBRARIES = android-common-chips
+LOCAL_STATIC_JAVA_LIBRARIES := android-common-chips
+LOCAL_STATIC_JAVA_LIBRARIES += guava
 
 LOCAL_SDK_VERSION := 14
 
diff --git a/res/layout/compose.xml b/res/layout/compose.xml
index 5781b63..ea870a3 100644
--- a/res/layout/compose.xml
+++ b/res/layout/compose.xml
@@ -47,14 +47,14 @@
             </LinearLayout>
 
             <!--  Attachments -->
-            <!--<com.google.android.gm.AttachmentsView android:id="@+id/attachments"
+            <com.android.email.compose.AttachmentsView android:id="@+id/attachments"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
                 android:orientation="vertical"
                 android:paddingTop="2dip"
                 android:paddingRight="5dip"
                 android:paddingBottom="0dip"
-                android:paddingLeft="5dip" />-->
+                android:paddingLeft="5dip" />
 
             <!-- Body -->
             <include layout="@layout/compose_body"/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ae18940..11a4882 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -56,6 +56,33 @@
     <!-- Menu item that displays the help page for Gmail. -->
     <string name="help_and_info">Help</string>
 
+    <!-- Attachments -->
+    <!-- Size unit, displayed in a button next to an attachment [CHAR LIMIT=5]-->
+    <string name="bytes">B</string>
+    <!-- Size unit, displayed in a button next to an attachment [CHAR LIMIT=5] -->
+    <string name="kilobytes">KB</string>
+    <!-- Size unit, displayed in a button next to an attachment  [CHAR LIMIT=5]-->
+    <string name="megabytes">MB</string>
+    <!-- Attachment description for image files [CHAR LIMIT=30] -->
+    <string name="attachment_image">Image</string>
+    <!-- Attachment description for video files [CHAR LIMIT=30] -->
+    <string name="attachment_video">Video</string>
+    <!-- Attachment description for audio files [CHAR LIMIT=30] -->
+    <string name="attachment_audio">Audio</string>
+    <!-- Attachment description for text files [CHAR LIMIT=30] -->
+    <string name="attachment_text">Text</string>
+    <!-- Attachment description for .doc files [CHAR LIMIT=30] -->
+    <string name="attachment_application_msword">Document</string>
+    <!-- Attachment description for .ppt files [CHAR LIMIT=30] -->
+    <string name="attachment_application_vnd_ms_powerpoint">Presentation</string>
+    <!-- Attachment description for .pdf files [CHAR LIMIT=30] -->
+    <string name="attachment_application_vnd_ms_excel">Spreadsheet</string>
+    <!-- Attachment description for .pdf files [CHAR LIMIT=30] -->
+    <string name="attachment_application_pdf">PDF</string>
+    <!-- Attachment description for unknown files [CHAR LIMIT=30]-->
+    <string name="attachment_unknown"><xliff:g id="attachmentExtension">%s</xliff:g> File</string>
+
+
     <!-- Webview Context Menu Strings -->
     <!-- Title of dialog for choosing which activity to share a link with. [CHAR LIMIT=50]-->
     <string name="choosertitle_sharevia">Share via</string>
diff --git a/src/com/android/email/compose/AttachmentComposeView.java b/src/com/android/email/compose/AttachmentComposeView.java
new file mode 100644
index 0000000..a376cd9
--- /dev/null
+++ b/src/com/android/email/compose/AttachmentComposeView.java
@@ -0,0 +1,70 @@
+/**
+ * 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.
+ */
+package com.android.email.compose;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.email.providers.protos.Attachment;
+import com.android.email.R;
+import com.android.email.utils.AttachmentUtils;
+import com.android.email.utils.Utils;
+import com.android.email.utils.LogUtils;
+
+/**
+ * This view is used in the ComposeActivity to display an attachment along with its name/size
+ * and a Remove button.
+ */
+class AttachmentComposeView extends LinearLayout {
+    private final long mSize;
+    private final String mFilename;
+
+    public AttachmentComposeView(Context c, Attachment attachment) {
+        super(c);
+        mFilename = attachment.getName();
+        mSize = attachment.getSize();
+
+        LogUtils.d(Utils.LOG_TAG, ">>>>> Attachment uri: %s", attachment.getOriginExtras());
+        LogUtils.d(Utils.LOG_TAG, ">>>>>           type: %s", attachment.getContentType());
+        LogUtils.d(Utils.LOG_TAG, ">>>>>           name: %s", mFilename);
+        LogUtils.d(Utils.LOG_TAG, ">>>>>           size: %d", mSize);
+
+        LayoutInflater factory = LayoutInflater.from(getContext());
+
+        factory.inflate(R.layout.attachment, this);
+        populateAttachmentData(c);
+    }
+
+    public void addDeleteListener(OnClickListener clickListener) {
+        ImageView deleteButton = (ImageView) findViewById(R.id.remove_attachment);
+        deleteButton.setOnClickListener(clickListener);
+    }
+
+    private void populateAttachmentData(Context context) {
+        ((TextView) findViewById(R.id.attachment_name)).setText(mFilename);
+
+        if (mSize != 0) {
+            ((TextView) findViewById(R.id.attachment_size)).
+                    setText(AttachmentUtils.convertToHumanReadableSize(context, mSize));
+        } else {
+            ((TextView) findViewById(R.id.attachment_size)).setVisibility(View.GONE);
+        }
+    }
+}
diff --git a/src/com/android/email/compose/AttachmentsView.java b/src/com/android/email/compose/AttachmentsView.java
new file mode 100644
index 0000000..9265984
--- /dev/null
+++ b/src/com/android/email/compose/AttachmentsView.java
@@ -0,0 +1,143 @@
+/**
+ * Copyright (c) 2011, 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.
+ */
+package com.android.email.compose;
+
+import com.android.email.providers.protos.Attachment;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+
+/*
+ * View for displaying attachments in the compose screen.
+ */
+class AttachmentsView extends LinearLayout {
+    private ArrayList<Attachment> mAttachments;
+    private AttachmentChangesListener mChangeListener;
+
+    public AttachmentsView(Context context) {
+        this(context, null);
+    }
+
+    public AttachmentsView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mAttachments = Lists.newArrayList();
+    }
+
+    /**
+     * Set a listener for changes to the attachments.
+     * @param listener
+     */
+    public void setAttachmentChangesListener(AttachmentChangesListener listener) {
+        mChangeListener = listener;
+    }
+
+    /**
+     * Add an attachment and update the ui accordingly.
+     * @param attachment
+     */
+    public void addAttachment(final Attachment attachment) {
+        if (!isShown()) {
+            setVisibility(View.VISIBLE);
+        }
+        mAttachments.add(attachment);
+
+        final AttachmentComposeView attachmentView =
+            new AttachmentComposeView(getContext(), attachment);
+
+        attachmentView.addDeleteListener(new OnClickListener() {
+            public void onClick(View v) {
+                deleteAttachment(attachmentView, attachment);
+            }
+        });
+
+
+        addView(attachmentView, new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.MATCH_PARENT,
+                LinearLayout.LayoutParams.MATCH_PARENT));
+
+        if (mChangeListener != null) {
+            mChangeListener.onAttachmentAdded();
+        }
+    }
+
+    @VisibleForTesting
+    protected void deleteAttachment(final AttachmentComposeView attachmentView,
+            final Attachment attachment) {
+        mAttachments.remove(attachment);
+        removeView(attachmentView);
+        if (mChangeListener != null) {
+            mChangeListener.onAttachmentDeleted();
+        }
+        if (mAttachments.size() == 0) {
+            setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Get all attachments being managed by this view.
+     * @return attachments.
+     */
+    public ArrayList<Attachment> getAttachments() {
+        return mAttachments;
+    }
+
+    /**
+     * Delete all attachments being managed by this view.
+     */
+    public void deleteAllAttachments() {
+        mAttachments.clear();
+        removeAllViews();
+    }
+
+    /**
+     * See if all the attachments in this view are synced.
+     */
+    public boolean areAttachmentsSynced() {
+        for (Attachment a : mAttachments) {
+            if (a.isSynced()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get the total size of all attachments currently in this view.
+     */
+    public int getTotalAttachmentsSize() {
+        int totalSize = 0;
+        for (Attachment attachment : mAttachments) {
+            totalSize += attachment.getSize();
+        }
+        return totalSize;
+    }
+
+    /**
+     * Interface to implement to be notified about changes to the attachments.
+     * @author mindyp@google.com
+     *
+     */
+    public interface AttachmentChangesListener {
+        public void onAttachmentDeleted();
+        public void onAttachmentAdded();
+    }
+}
diff --git a/src/com/android/email/compose/ComposeActivity.java b/src/com/android/email/compose/ComposeActivity.java
index c5edd4d..31d8421 100644
--- a/src/com/android/email/compose/ComposeActivity.java
+++ b/src/com/android/email/compose/ComposeActivity.java
@@ -27,12 +27,17 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
+
+import com.android.email.providers.protos.Attachment;
+import com.android.email.providers.protos.mock.MockAttachment;
 import com.android.email.R;
+import com.android.email.utils.MimeType;
 
 public class ComposeActivity extends Activity implements OnClickListener {
 
     private Button mCcBccButton;
     private CcBccView mCcBccView;
+    private AttachmentsView mAttachmentsView;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -43,6 +48,7 @@
             mCcBccButton.setOnClickListener(this);
         }
         mCcBccView = (CcBccView) findViewById(R.id.cc_bcc_wrapper);
+        mAttachmentsView = (AttachmentsView)findViewById(R.id.attachments);
     }
 
     @Override
@@ -71,6 +77,14 @@
         int id = item.getItemId();
         boolean handled = false;
         switch (id) {
+            case R.id.add_attachment:
+                MockAttachment attachment = new MockAttachment();
+                attachment.partId = "0";
+                attachment.name = "testattachment.png";
+                attachment.contentType = MimeType.inferMimeType(attachment.name, null);
+                attachment.originExtras = "";
+                mAttachmentsView.addAttachment(attachment);
+                break;
             case R.id.add_cc:
             case R.id.add_bcc:
                 mCcBccView.show();
diff --git a/src/com/android/email/providers/protos/Attachment.java b/src/com/android/email/providers/protos/Attachment.java
new file mode 100644
index 0000000..6a2073e
--- /dev/null
+++ b/src/com/android/email/providers/protos/Attachment.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2011, 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.
+ */
+package com.android.email.providers.protos;
+
+public interface Attachment {
+
+    String getName();
+
+    long getSize();
+
+    String getOriginExtras();
+
+    String getContentType();
+
+    Object getOrigin();
+
+    String getPartId();
+
+    boolean isSynced();
+}
diff --git a/src/com/android/email/providers/protos/exchange/ExchangeAttachment.java b/src/com/android/email/providers/protos/exchange/ExchangeAttachment.java
new file mode 100644
index 0000000..d4ccf1b
--- /dev/null
+++ b/src/com/android/email/providers/protos/exchange/ExchangeAttachment.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2011, 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.
+ */
+package com.android.email.providers.protos.exchange;
+
+import com.android.email.providers.protos.Attachment;
+
+import java.io.Serializable;
+
+public class ExchangeAttachment implements Serializable, Attachment {
+    private static final long serialVersionUID = 1L;
+
+    public String mFileName;
+    public String mMimeType;
+    public long mSize;
+    public String mContentId;
+    public String mContentUri;
+    public long mMessageKey;
+    public String mLocation;
+    public String mEncoding;
+    public String mContent; // Not currently used
+    public int mFlags;
+    public byte[] mContentBytes;
+    public long mAccountKey;
+
+
+    @Override
+    public String getName() {
+        return mFileName;
+    }
+
+    @Override
+    public long getSize() {
+        return mSize;
+    }
+
+    @Override
+    public String getOriginExtras() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public String getContentType() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public Object getOrigin() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public String getPartId() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public boolean isSynced() {
+        // TODO Auto-generated method stub
+        return false;
+    }
+}
diff --git a/src/com/android/email/providers/protos/exchange/README b/src/com/android/email/providers/protos/exchange/README
deleted file mode 100644
index a3fcb75..0000000
--- a/src/com/android/email/providers/protos/exchange/README
+++ /dev/null
@@ -1 +0,0 @@
-Unified email directories
\ No newline at end of file
diff --git a/src/com/android/email/providers/protos/longshadow/README b/src/com/android/email/providers/protos/longshadow/README
deleted file mode 100644
index a3fcb75..0000000
--- a/src/com/android/email/providers/protos/longshadow/README
+++ /dev/null
@@ -1 +0,0 @@
-Unified email directories
\ No newline at end of file
diff --git a/src/com/android/email/providers/protos/mock/MockAttachment.java b/src/com/android/email/providers/protos/mock/MockAttachment.java
new file mode 100644
index 0000000..3a884af
--- /dev/null
+++ b/src/com/android/email/providers/protos/mock/MockAttachment.java
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) 2011, 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.
+ */
+package com.android.email.providers.protos.mock;
+
+import com.android.email.providers.protos.Attachment;
+
+import java.io.Serializable;
+
+
+
+public class MockAttachment implements Serializable, Attachment {
+
+    private static final long serialVersionUID = 1L;
+
+    /** Identifies the attachment uniquely when combined wih a message id.*/
+    public String partId;
+
+    /** The intended filename of the attachment.*/
+    public String name;
+
+    /** The native content type.*/
+    public String contentType;
+
+    /** The size of the attachment in its native form.*/
+    public int size;
+
+    /**
+     * The content type of the simple version of the attachment. Blank if no simple version is
+     * available.
+     */
+    public String simpleContentType;
+
+    public String originExtras;
+
+    public String cachedContent;
+
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public long getSize() {
+        return size;
+    }
+
+    @Override
+    public String getOriginExtras() {
+        return originExtras;
+    }
+
+    @Override
+    public String getContentType() {
+        return contentType;
+    }
+
+    @Override
+    public Object getOrigin() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public String getPartId() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public boolean isSynced() {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/email/utils/AttachmentUtils.java b/src/com/android/email/utils/AttachmentUtils.java
new file mode 100644
index 0000000..2e9a03a
--- /dev/null
+++ b/src/com/android/email/utils/AttachmentUtils.java
@@ -0,0 +1,28 @@
+package com.android.email.utils;
+
+import android.content.Context;
+
+import com.android.email.R;
+
+import java.text.DecimalFormat;
+
+public class AttachmentUtils {
+    private static final int KILO = 1024;
+    private static final int MEGA = KILO * KILO;
+
+    /**
+     * @return A string suitable for display in bytes, kilobytes or megabytes
+     *         depending on its size.
+     */
+    public static String convertToHumanReadableSize(Context context, long size) {
+        if (size < KILO) {
+            return size + context.getString(R.string.bytes);
+        } else if (size < MEGA) {
+            return (size / KILO) + context.getString(R.string.kilobytes);
+        } else {
+            DecimalFormat onePlace = new DecimalFormat("0.#");
+            return onePlace.format((float) size / (float) MEGA)
+                    + context.getString(R.string.megabytes);
+        }
+    }
+}
diff --git a/src/com/android/email/utils/LogUtils.java b/src/com/android/email/utils/LogUtils.java
new file mode 100644
index 0000000..8c065a0
--- /dev/null
+++ b/src/com/android/email/utils/LogUtils.java
@@ -0,0 +1,374 @@
+/**
+ * Copyright (c) 2011, 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.
+ */
+package com.android.email.utils;
+
+import android.net.Uri;
+import android.util.Log;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.List;
+
+public class LogUtils {
+    private static final String TAG = "Gmail";
+
+    /**
+     * Priority constant for the println method; use LogUtils.v.
+     */
+    public static final int VERBOSE = Log.VERBOSE;
+
+    /**
+     * Priority constant for the println method; use LogUtils.d.
+     */
+    public static final int DEBUG = Log.DEBUG;
+
+    /**
+     * Priority constant for the println method; use LogUtils.i.
+     */
+    public static final int INFO = Log.INFO;
+
+    /**
+     * Priority constant for the println method; use LogUtils.w.
+     */
+    public static final int WARN = Log.WARN;
+
+    /**
+     * Priority constant for the println method; use LogUtils.e.
+     */
+    public static final int ERROR = Log.ERROR;
+
+    /**
+     * Used to enable/disable logging that we don't want included in
+     * production releases.
+     */
+    private static final int MAX_ENABLED_LOG_LEVEL = VERBOSE;
+
+
+    private static Boolean sDebugLoggingEnabledForTests = null;
+
+    /**
+     * Enable debug logging for unit tests.
+     */
+    @VisibleForTesting
+    static void setDebugLoggingEnabledForTests(boolean enabled) {
+        sDebugLoggingEnabledForTests = Boolean.valueOf(enabled);
+    }
+
+    /**
+     * Returns true if the build configuration prevents debug logging.
+     */
+    @VisibleForTesting
+    static boolean buildPreventsDebugLogging() {
+        return MAX_ENABLED_LOG_LEVEL > VERBOSE;
+    }
+
+    /**
+     * Returns a boolean indicating whether debug logging is enabled.
+     */
+    private static boolean isDebugLoggingEnabled() {
+        if (buildPreventsDebugLogging()) {
+            return false;
+        }
+        if (sDebugLoggingEnabledForTests != null) {
+            return sDebugLoggingEnabledForTests.booleanValue();
+        }
+        return Log.isLoggable(TAG, Log.DEBUG);
+    }
+
+    /**
+     * Returns a String for the specified content provider uri.  This will do
+     * sanitation of the uri to remove PII if debug logging is not enabled.
+     */
+    public static String contentUriToString(Uri uri) {
+
+        if (isDebugLoggingEnabled()) {
+            // Debug logging has been enabled, so log the uri as is
+            return uri.toString();
+        } else {
+            // Debug logging is not enabled, we want to remove the email address from the uri.
+            List<String> pathSegments = uri.getPathSegments();
+
+            Uri.Builder builder = new Uri.Builder()
+                    .scheme(uri.getScheme())
+                    .authority(uri.getAuthority())
+                    .query(uri.getQuery())
+                    .fragment(uri.getFragment());
+
+            // This assumes that the first path segment is the account
+            final String account = pathSegments.get(0);
+
+            builder = builder.appendPath(String.valueOf(account.hashCode()));
+            for (int i = 1; i < pathSegments.size(); i++) {
+                builder.appendPath(pathSegments.get(i));
+            }
+            return builder.toString();
+        }
+    }
+
+   /* TODO: what is the correct behavior for base case and the Gmail case? Seems like this
+    * belongs in override code in UnifiedGmail.
+    *Converts the specified set of labels to a string, and removes any PII as necessary
+    * public static String labelSetToString(Set<String> labelSet) {
+        if (isDebugLoggingEnabled() || labelSet == null) {
+            return labelSet != null ? labelSet.toString() : "";
+        } else {
+            final StringBuilder builder = new StringBuilder("[");
+            int i = 0;
+            for(String label : labelSet) {
+                if (i > 0) {
+                    builder.append(", ");
+                }
+                builder.append(sanitizeLabelName(label));
+                i++;
+            }
+            builder.append(']');
+            return builder.toString();
+        }
+    }
+
+    private static String sanitizeLabelName(String canonicalName) {
+        if (TextUtils.isEmpty(canonicalName)) {
+            return "";
+        }
+
+        if (Gmail.isSystemLabel(canonicalName)) {
+            return canonicalName;
+        }
+
+        return USER_LABEL_PREFIX + String.valueOf(canonicalName.hashCode());
+    }*/
+
+    /**
+     * Checks to see whether or not a log for the specified tag is loggable at the specified level.
+     */
+    public static boolean isLoggable(String tag, int level) {
+        if (MAX_ENABLED_LOG_LEVEL > level) {
+            return false;
+        }
+        return Log.isLoggable(tag, level);
+    }
+
+    /**
+     * Send a {@link #VERBOSE} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int v(String tag, String format, Object... args) {
+        if (isLoggable(tag, VERBOSE)) {
+            return Log.v(tag, String.format(format, args));
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #VERBOSE} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int v(String tag, Throwable tr, String format, Object... args) {
+        if (isLoggable(tag, VERBOSE)) {
+            return Log.v(tag, String.format(format, args), tr);
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #DEBUG} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int d(String tag, String format, Object... args) {
+        if (isLoggable(tag, DEBUG)) {
+            return Log.d(tag, String.format(format, args));
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #DEBUG} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int d(String tag, Throwable tr, String format, Object... args) {
+        if (isLoggable(tag, DEBUG)) {
+            return Log.d(tag, String.format(format, args), tr);
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #INFO} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int i(String tag, String format, Object... args) {
+        if (isLoggable(tag, INFO)) {
+            return Log.i(tag, String.format(format, args));
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #INFO} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int i(String tag, Throwable tr, String format, Object... args) {
+        if (isLoggable(tag, INFO)) {
+            return Log.i(tag, String.format(format, args), tr);
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #WARN} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int w(String tag, String format, Object... args) {
+        if (isLoggable(tag, WARN)) {
+            return Log.w(tag, String.format(format, args));
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #WARN} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int w(String tag, Throwable tr, String format, Object... args) {
+        if (isLoggable(tag, WARN)) {
+            return Log.w(tag, String.format(format, args), tr);
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #ERROR} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int e(String tag, String format, Object... args) {
+        if (isLoggable(tag, ERROR)) {
+            return Log.e(tag, String.format(format, args));
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #ERROR} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int e(String tag, Throwable tr, String format, Object... args) {
+        if (isLoggable(tag, ERROR)) {
+            return Log.e(tag, String.format(format, args), tr);
+        }
+        return 0;
+    }
+
+    /**
+     * What a Terrible Failure: Report a condition that should never happen.
+     * The error will always be logged at level ASSERT with the call stack.
+     * Depending on system configuration, a report may be added to the
+     * {@link android.os.DropBoxManager} and/or the process may be terminated
+     * immediately with an error dialog.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int wtf(String tag, String format, Object... args) {
+        return Log.wtf(tag, String.format(format, args));
+    }
+
+    /**
+     * What a Terrible Failure: Report a condition that should never happen.
+     * The error will always be logged at level ASSERT with the call stack.
+     * Depending on system configuration, a report may be added to the
+     * {@link android.os.DropBoxManager} and/or the process may be terminated
+     * immediately with an error dialog.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int wtf(String tag, Throwable tr, String format, Object... args) {
+        return Log.wtf(tag, String.format(format, args), tr);
+    }
+}
diff --git a/src/com/android/email/utils/MimeType.java b/src/com/android/email/utils/MimeType.java
new file mode 100644
index 0000000..b27c300
--- /dev/null
+++ b/src/com/android/email/utils/MimeType.java
@@ -0,0 +1,173 @@
+/**
+ * Copyright (c) 2010, 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.
+ */
+package com.android.email.utils;
+
+import android.text.TextUtils;
+import android.webkit.MimeTypeMap;
+import com.google.common.collect.ImmutableSet;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utilities for working with different content types within Gmail.
+ */
+public class MimeType {
+    public static final String ANDROID_ARCHIVE = "application/vnd.android.package-archive";
+    private static final String TEXT_PLAIN = "text/plain";
+    @VisibleForTesting
+    static final String GENERIC_MIMETYPE = "application/octet-stream";
+
+    @VisibleForTesting
+    static final String EML_ATTACHMENT_CONTENT_TYPE = "application/eml";
+    private static final String NULL_ATTACHMENT_CONTENT_TYPE = "null";
+    private static final Set<String> UNACCEPTABLE_ATTACHMENT_TYPES = ImmutableSet.of(
+            "application/zip", "application/x-gzip", "application/x-bzip2",
+            "application/x-compress", "application/x-compressed", "application/x-tar");
+
+    private static Set<String> sGviewSupportedTypes = ImmutableSet.of(
+            "application/pdf",
+            "application/vnd.ms-powerpoint",
+            "image/tiff",
+            "application/msword",
+            "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+            "application/vnd.openxmlformats-officedocument.presentationml.presentation");
+
+    /**
+     * Returns whether or not an attachment of the specified type is installable (e.g. an apk).
+     */
+    public static boolean isInstallable(String type) {
+        return ANDROID_ARCHIVE.equals(type);
+    }
+
+    /**
+     * Returns whether or not an attachment of the specified type is playable (e.g. a video).
+     */
+    public static boolean isPlayable(String type) {
+        return type.startsWith("video/") || type.startsWith("audio/");
+    }
+
+    /**
+     * Returns whether or not an attachment of the specified type is viewable.
+     */
+    public static boolean isViewable(Context context, Uri contentUri, String contentType) {
+        // The provider returns a contentType of "null" instead of null, when the
+        // content type is not known.  Changing the provider to return null,
+        // breaks other areas that will need to be fixed in a later CL.
+        // Bug 2922948 has been written up to track this
+        if (contentType == null || contentType.length() == 0 ||
+                NULL_ATTACHMENT_CONTENT_TYPE.equals(contentType)) {
+            return false;
+        }
+
+        if (isBlocked(contentType)) {
+            return false;
+        }
+
+        Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
+
+        mimetypeIntent.setDataAndType(contentUri, contentType);
+        PackageManager manager;
+        // We need to catch the exception to make CanvasConversationHeaderView
+        // test pass.  Bug: http://b/issue?id=3470653.
+        try {
+            manager = context.getPackageManager();
+        } catch (UnsupportedOperationException e) {
+            return false;
+        }
+        List<ResolveInfo> list = manager.queryIntentActivities(mimetypeIntent,
+            PackageManager.MATCH_DEFAULT_ONLY);
+        return list.size() > 0;
+    }
+
+    /**
+     * @return whether the specified type is blocked.
+     */
+    public static boolean isBlocked(String contentType) {
+        return UNACCEPTABLE_ATTACHMENT_TYPES.contains(contentType);
+    }
+
+    /* TODO: what do we want to do about GSF keys for the unified app?
+    public static boolean isPreviewable(Context context, String contentType) {
+        final String supportedTypes = Gservices.getString(
+                context.getContentResolver(), GservicesKeys.GMAIL_GVIEW_SUPPORTED_TYPES);
+        if (supportedTypes != null) {
+            sGviewSupportedTypes = ImmutableSet.of(supportedTypes.split(","));
+        }
+        return sGviewSupportedTypes.contains(contentType);
+    }*/
+
+    /**
+     * Extract and return filename's extension, converted to lower case, and not including the "."
+     *
+     * @return extension, or null if not found (or null/empty filename)
+     */
+    private static String getFilenameExtension(String fileName) {
+        String extension = null;
+        if (!TextUtils.isEmpty(fileName)) {
+            int lastDot = fileName.lastIndexOf('.');
+            if ((lastDot > 0) && (lastDot < fileName.length() - 1)) {
+                extension = fileName.substring(lastDot + 1).toLowerCase();
+            }
+        }
+        return extension;
+    }
+
+
+    /**
+     * Returns the mime type of the attachment based on its name and
+     * original mime type. This is an workaround for bugs where Gmail
+     * server doesn't set content-type for certain types correctly.
+     * 1) EML files -> "application/eml".
+     * @param name name of the attachment.
+     * @param mimeType original mime type of the attachment.
+     * @return the inferred mime type of the attachment.
+     */
+    public static String inferMimeType(String name, String mimeType) {
+        final String extension = getFilenameExtension(name);
+        if (TextUtils.isEmpty(extension)) {
+            // Attachment doesn't have extension, just return original mime
+            // type.
+            return mimeType;
+        } else {
+            final boolean isTextPlain = TEXT_PLAIN.equalsIgnoreCase(mimeType);
+            final boolean isGenericType =
+                    isTextPlain || GENERIC_MIMETYPE.equalsIgnoreCase(mimeType);
+
+            String type = null;
+            if (isGenericType || TextUtils.isEmpty(mimeType)) {
+                type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+            }
+            if (!TextUtils.isEmpty(type)) {
+                return type;
+            } if (extension.equals("eml")) {
+                // Extension is ".eml", return mime type "application/eml"
+                return EML_ATTACHMENT_CONTENT_TYPE;
+            } else {
+                // Extension is not ".eml", just return original mime type.
+                return !TextUtils.isEmpty(mimeType) ? mimeType : GENERIC_MIMETYPE;
+            }
+        }
+    }
+}
diff --git a/src/com/android/email/utils/README b/src/com/android/email/utils/README
deleted file mode 100644
index a3fcb75..0000000
--- a/src/com/android/email/utils/README
+++ /dev/null
@@ -1 +0,0 @@
-Unified email directories
\ No newline at end of file
diff --git a/src/com/android/email/utils/Utils.java b/src/com/android/email/utils/Utils.java
new file mode 100644
index 0000000..fd6a587
--- /dev/null
+++ b/src/com/android/email/utils/Utils.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2011, 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.
+ */
+package com.android.email.utils;
+
+public class Utils {
+    public static final String LOG_TAG = "Email";
+
+    public String getLogTag() {
+        return LOG_TAG;
+    }
+}