Merge changes from topic 'refactor-voicemail-archive' into nyc-dev

* changes:
  Add share voicemail functionality.
  Refactor voicemail archive code.
diff --git a/res/xml/file_paths.xml b/res/xml/file_paths.xml
index 45caed2..365a603 100644
--- a/res/xml/file_paths.xml
+++ b/res/xml/file_paths.xml
@@ -16,5 +16,7 @@
 
 <paths xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Offer access to files under Context.getCacheDir() -->
-    <cache-path name="my_cache" />
+     <cache-path name="my_cache" />
+     <!-- Offer access to voicemail folder under Context.getFilesDir() -->
+     <files-path name="voicemails" path="voicemails/" />
 </paths>
diff --git a/src/com/android/dialer/database/VoicemailArchiveProvider.java b/src/com/android/dialer/database/VoicemailArchiveProvider.java
index 79b7a76..b3306bc 100644
--- a/src/com/android/dialer/database/VoicemailArchiveProvider.java
+++ b/src/com/android/dialer/database/VoicemailArchiveProvider.java
@@ -28,6 +28,7 @@
 import android.os.ParcelFileDescriptor;
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
+import android.webkit.MimeTypeMap;
 
 import com.android.dialerbind.DatabaseHelperManager;
 import com.google.common.annotations.VisibleForTesting;
@@ -119,7 +120,11 @@
 
         // Create new file only if path is not provided to one
         if (!values.containsKey(VoicemailArchiveContract.VoicemailArchive._DATA)) {
-            File voicemailFile = new File(directory, Long.toString(id));
+            String fileExtension = MimeTypeMap.getSingleton().getExtensionFromMimeType(
+                    values.getAsString(VoicemailArchiveContract.VoicemailArchive.MIME_TYPE));
+            File voicemailFile = new File(directory,
+                    TextUtils.isEmpty(fileExtension) ? Long.toString(id) :
+                            id + "." + fileExtension);
             values.put(VoicemailArchiveContract.VoicemailArchive._DATA, voicemailFile.getPath());
         }
         update(newUri, values, null, null);
diff --git a/src/com/android/dialer/voicemail/VoicemailArchivePlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailArchivePlaybackPresenter.java
index 050b8ac..5f73d16 100644
--- a/src/com/android/dialer/voicemail/VoicemailArchivePlaybackPresenter.java
+++ b/src/com/android/dialer/voicemail/VoicemailArchivePlaybackPresenter.java
@@ -62,7 +62,6 @@
                     }
                 } catch (FileNotFoundException e) {
                     Log.d(TAG, "Voicemail file not found for " + mVoicemailUri);
-                    handleError(e);
                 }
                 return false;
             }
@@ -75,11 +74,17 @@
     }
 
     @Override
-    protected boolean requestContent(int code) {
-        if (mContext == null || mVoicemailUri == null) {
-            return false;
+    protected void startArchiveVoicemailTask(final Uri voicemailUri, final boolean archivedByUser) {
+        // If a user wants to share an archived voicemail, no need for archiving, just go straight
+        // to share intent.
+        if (!archivedByUser) {
+            sendShareIntent(voicemailUri);
         }
-        prepareContent();
-        return true;
+    }
+
+    @Override
+    protected boolean requestContent(int code) {
+        handleError(new FileNotFoundException("Voicemail archive file does not exist"));
+        return false;       // No way for archive tab to request content
     }
 }
diff --git a/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtil.java b/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtil.java
new file mode 100644
index 0000000..7abf9a7
--- /dev/null
+++ b/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtil.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2016 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.dialer.voicemail;
+
+import com.android.contacts.common.testing.NeededForTesting;
+import com.android.dialer.calllog.CallLogQuery;
+import com.android.dialer.database.VoicemailArchiveContract;
+import com.android.dialer.util.AsyncTaskExecutor;
+import com.android.dialer.util.AsyncTaskExecutors;
+import com.google.common.base.Preconditions;
+import com.google.common.io.ByteStreams;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.CallLog;
+import android.provider.VoicemailContract;
+import android.util.Log;
+import com.android.common.io.MoreCloseables;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.annotation.Nullable;
+
+/**
+ * Class containing asynchronous tasks for voicemails.
+ */
+@NeededForTesting
+public class VoicemailAsyncTaskUtil {
+    private static final String TAG = "VoicemailAsyncTaskUtil";
+
+    /** The enumeration of {@link AsyncTask} objects we use in this class. */
+    public enum Tasks {
+        GET_VOICEMAIL_FILE_PATH,
+        SET_VOICEMAIL_ARCHIVE_STATUS,
+        ARCHIVE_VOICEMAIL_CONTENT
+    }
+
+    @NeededForTesting
+    public interface OnArchiveVoicemailListener {
+        /**
+         * Called after the voicemail has been archived.
+         *
+         * @param archivedVoicemailUri the URI of the archived voicemail
+         */
+        void onArchiveVoicemail(@Nullable Uri archivedVoicemailUri);
+    }
+
+    @NeededForTesting
+    public interface OnSetVoicemailArchiveStatusListener {
+        /**
+         * Called after the voicemail archived_by_user column is updated.
+         *
+         * @param success whether the update was successful or not
+         */
+        void onSetVoicemailArchiveStatus(boolean success);
+    }
+
+    @NeededForTesting
+    public interface OnGetArchivedVoicemailFilePathListener {
+        /**
+         * Called after the voicemail file path is obtained.
+         *
+         * @param filePath the file path of the archived voicemail
+         */
+        void onGetArchivedVoicemailFilePath(@Nullable String filePath);
+    }
+
+    private final ContentResolver mResolver;
+    private final AsyncTaskExecutor mAsyncTaskExecutor;
+
+    @NeededForTesting
+    public VoicemailAsyncTaskUtil(ContentResolver contentResolver) {
+        mResolver = Preconditions.checkNotNull(contentResolver);
+        mAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor();
+    }
+
+    /**
+     * Returns the archived voicemail file path.
+     */
+    @NeededForTesting
+    public void getVoicemailFilePath(
+            final OnGetArchivedVoicemailFilePathListener listener,
+            final Uri voicemailUri) {
+        Preconditions.checkNotNull(listener);
+        Preconditions.checkNotNull(voicemailUri);
+        mAsyncTaskExecutor.submit(Tasks.GET_VOICEMAIL_FILE_PATH,
+                new AsyncTask<Void, Void, String>() {
+                    @Nullable
+                    @Override
+                    protected String doInBackground(Void... params) {
+                        try (Cursor cursor = mResolver.query(voicemailUri,
+                                new String[]{VoicemailArchiveContract.VoicemailArchive._DATA},
+                                null, null, null)) {
+                            if (hasContent(cursor)) {
+                                return cursor.getString(cursor.getColumnIndex(
+                                        VoicemailArchiveContract.VoicemailArchive._DATA));
+                            }
+                        }
+                        return null;
+                    }
+
+                    @Override
+                    protected void onPostExecute(String filePath) {
+                        listener.onGetArchivedVoicemailFilePath(filePath);
+                    }
+                });
+    }
+
+    /**
+     * Updates the archived_by_user flag of the archived voicemail.
+     */
+    @NeededForTesting
+    public void setVoicemailArchiveStatus(
+            final OnSetVoicemailArchiveStatusListener listener,
+            final Uri voicemailUri,
+            final boolean archivedByUser) {
+        Preconditions.checkNotNull(listener);
+        Preconditions.checkNotNull(voicemailUri);
+        mAsyncTaskExecutor.submit(Tasks.SET_VOICEMAIL_ARCHIVE_STATUS,
+                new AsyncTask<Void, Void, Boolean>() {
+                    @Override
+                    protected Boolean doInBackground(Void... params) {
+                        ContentValues values = new ContentValues(1);
+                        values.put(VoicemailArchiveContract.VoicemailArchive.ARCHIVED,
+                                archivedByUser);
+                        return mResolver.update(voicemailUri, values, null, null) > 0;
+                    }
+
+                    @Override
+                    protected void onPostExecute(Boolean success) {
+                        listener.onSetVoicemailArchiveStatus(success);
+                    }
+                });
+    }
+
+    /**
+     * Checks if a voicemail has already been archived, if so, return the previously archived URI.
+     * Otherwise, copy the voicemail information to the local dialer database. If archive was
+     * successful, archived voicemail URI is returned to listener, otherwise null.
+     */
+    @NeededForTesting
+    public void archiveVoicemailContent(
+            final OnArchiveVoicemailListener listener,
+            final Uri voicemailUri) {
+        Preconditions.checkNotNull(listener);
+        Preconditions.checkNotNull(voicemailUri);
+        mAsyncTaskExecutor.submit(Tasks.ARCHIVE_VOICEMAIL_CONTENT,
+                new AsyncTask<Void, Void, Uri>() {
+                    @Nullable
+                    @Override
+                    protected Uri doInBackground(Void... params) {
+                        Uri archivedVoicemailUri = getArchivedVoicemailUri(voicemailUri);
+
+                        // If previously archived, return uri, otherwise archive everything.
+                        if (archivedVoicemailUri != null) {
+                            return archivedVoicemailUri;
+                        }
+
+                        // Combine call log and voicemail content info.
+                        ContentValues values = getVoicemailContentValues(voicemailUri);
+                        if (values == null) {
+                            return null;
+                        }
+
+                        Uri insertedVoicemailUri = mResolver.insert(
+                                VoicemailArchiveContract.VoicemailArchive.CONTENT_URI, values);
+                        if (insertedVoicemailUri == null) {
+                            return null;
+                        }
+
+                        // Copy voicemail content to a new file.
+                        boolean copiedFile = false;
+                        try (InputStream inputStream = mResolver.openInputStream(voicemailUri);
+                             OutputStream outputStream =
+                                     mResolver.openOutputStream(insertedVoicemailUri)) {
+                            if (inputStream != null && outputStream != null) {
+                                ByteStreams.copy(inputStream, outputStream);
+                                copiedFile = true;
+                                return insertedVoicemailUri;
+                            }
+                        } catch (IOException e) {
+                            Log.w(TAG, "Failed to copy voicemail content to new file: "
+                                    + e.toString());
+                        } finally {
+                            if (!copiedFile) {
+                                // Roll back insert if the voicemail content was not copied.
+                                mResolver.delete(insertedVoicemailUri, null, null);
+                            }
+                        }
+                        return null;
+                    }
+
+                    @Override
+                    protected void onPostExecute(Uri archivedVoicemailUri) {
+                        listener.onArchiveVoicemail(archivedVoicemailUri);
+                    }
+                });
+    }
+
+    /**
+     * Helper method to get the archived URI of a voicemail.
+     *
+     * @param voicemailUri a {@link android.provider.VoicemailContract.Voicemails#CONTENT_URI} URI.
+     * @return the URI of the archived voicemail or {@code null}
+     */
+    @Nullable
+    private Uri getArchivedVoicemailUri(Uri voicemailUri) {
+        try (Cursor cursor = getArchiveExistsCursor(voicemailUri)) {
+            if (hasContent(cursor)) {
+                return VoicemailArchiveContract.VoicemailArchive
+                        .buildWithId(cursor.getInt(cursor.getColumnIndex(
+                                VoicemailArchiveContract.VoicemailArchive._ID)));
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Helper method to make a copy of all the values needed to display a voicemail.
+     *
+     * @param voicemailUri a {@link VoicemailContract.Voicemails#CONTENT_URI} URI.
+     * @return the combined call log and voicemail values for the given URI, or {@code null}
+     */
+    @Nullable
+    private ContentValues getVoicemailContentValues(Uri voicemailUri) {
+        try (Cursor callLogInfo = getCallLogInfoCursor(voicemailUri);
+             Cursor contentInfo = getContentInfoCursor(voicemailUri)) {
+
+            if (hasContent(callLogInfo) && hasContent(contentInfo)) {
+                // Create values to insert into database.
+                ContentValues values = new ContentValues();
+
+                // Insert voicemail call log info.
+                values.put(VoicemailArchiveContract.VoicemailArchive.COUNTRY_ISO,
+                        callLogInfo.getString(CallLogQuery.COUNTRY_ISO));
+                values.put(VoicemailArchiveContract.VoicemailArchive.GEOCODED_LOCATION,
+                        callLogInfo.getString(CallLogQuery.GEOCODED_LOCATION));
+                values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NAME,
+                        callLogInfo.getString(CallLogQuery.CACHED_NAME));
+                values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_TYPE,
+                        callLogInfo.getInt(CallLogQuery.CACHED_NUMBER_TYPE));
+                values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_LABEL,
+                        callLogInfo.getString(CallLogQuery.CACHED_NUMBER_LABEL));
+                values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_LOOKUP_URI,
+                        callLogInfo.getString(CallLogQuery.CACHED_LOOKUP_URI));
+                values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_MATCHED_NUMBER,
+                        callLogInfo.getString(CallLogQuery.CACHED_MATCHED_NUMBER));
+                values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NORMALIZED_NUMBER,
+                        callLogInfo.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER));
+                values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_FORMATTED_NUMBER,
+                        callLogInfo.getString(CallLogQuery.CACHED_FORMATTED_NUMBER));
+                values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER_PRESENTATION,
+                        callLogInfo.getInt(CallLogQuery.NUMBER_PRESENTATION));
+                values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_COMPONENT_NAME,
+                        callLogInfo.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME));
+                values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_ID,
+                        callLogInfo.getString(CallLogQuery.ACCOUNT_ID));
+                values.put(VoicemailArchiveContract.VoicemailArchive.FEATURES,
+                        callLogInfo.getInt(CallLogQuery.FEATURES));
+                values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_PHOTO_URI,
+                        callLogInfo.getString(CallLogQuery.CACHED_PHOTO_URI));
+
+                // Insert voicemail content info.
+                values.put(VoicemailArchiveContract.VoicemailArchive.SERVER_ID,
+                        contentInfo.getInt(contentInfo.getColumnIndex(
+                                VoicemailContract.Voicemails._ID)));
+                values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER,
+                        contentInfo.getString(contentInfo.getColumnIndex(
+                                VoicemailContract.Voicemails.NUMBER)));
+                values.put(VoicemailArchiveContract.VoicemailArchive.DATE,
+                        contentInfo.getLong(contentInfo.getColumnIndex(
+                                VoicemailContract.Voicemails.DATE)));
+                values.put(VoicemailArchiveContract.VoicemailArchive.DURATION,
+                        contentInfo.getLong(contentInfo.getColumnIndex(
+                                VoicemailContract.Voicemails.DURATION)));
+                values.put(VoicemailArchiveContract.VoicemailArchive.MIME_TYPE,
+                        contentInfo.getString(contentInfo.getColumnIndex(
+                                VoicemailContract.Voicemails.MIME_TYPE)));
+                values.put(VoicemailArchiveContract.VoicemailArchive.TRANSCRIPTION,
+                        contentInfo.getString(contentInfo.getColumnIndex(
+                                VoicemailContract.Voicemails.TRANSCRIPTION)));
+
+                // Achived is false by default because it is updated after insertion.
+                values.put(VoicemailArchiveContract.VoicemailArchive.ARCHIVED, false);
+
+                return values;
+            }
+        }
+        return null;
+    }
+
+    private boolean hasContent(@Nullable Cursor cursor) {
+        return cursor != null && cursor.moveToFirst();
+    }
+
+    @Nullable
+    private Cursor getCallLogInfoCursor(Uri voicemailUri) {
+        return mResolver.query(
+                ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
+                        ContentUris.parseId(voicemailUri)),
+                CallLogQuery._PROJECTION, null, null, null);
+    }
+
+    @Nullable
+    private Cursor getContentInfoCursor(Uri voicemailUri) {
+        return mResolver.query(voicemailUri,
+                new String[] {
+                        VoicemailContract.Voicemails._ID,
+                        VoicemailContract.Voicemails.NUMBER,
+                        VoicemailContract.Voicemails.DATE,
+                        VoicemailContract.Voicemails.DURATION,
+                        VoicemailContract.Voicemails.MIME_TYPE,
+                        VoicemailContract.Voicemails.TRANSCRIPTION,
+                }, null, null, null);
+    }
+
+    @Nullable
+    private Cursor getArchiveExistsCursor(Uri voicemailUri) {
+        return mResolver.query(VoicemailArchiveContract.VoicemailArchive.CONTENT_URI,
+                new String[] {VoicemailArchiveContract.VoicemailArchive._ID},
+                VoicemailArchiveContract.VoicemailArchive.SERVER_ID + "="
+                        + ContentUris.parseId(voicemailUri),
+                null,
+                null);
+    }
+}
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
index 8c869d1..e5efbec 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
@@ -258,6 +258,17 @@
         }
     };
 
+    private final View.OnClickListener mShareButtonListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (mPresenter == null || isArchiving(mVoicemailUri)) {
+                return;
+            }
+            disableUiElements();
+            mPresenter.archiveContent(mVoicemailUri, false);
+        }
+    };
+
     private Context mContext;
     private VoicemailPlaybackPresenter mPresenter;
     private Uri mVoicemailUri;
@@ -339,6 +350,7 @@
         mPlaybackSpeakerphone.setOnClickListener(mSpeakerphoneListener);
         mDeleteButton.setOnClickListener(mDeleteButtonListener);
         mArchiveButton.setOnClickListener(mArchiveButtonListener);
+        mShareButton.setOnClickListener(mShareButtonListener);
 
         mPositionText.setText(formatAsMinutesAndSeconds(0));
         mTotalDurationText.setText(formatAsMinutesAndSeconds(0));
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
index 3151a5e..5924fb4 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
@@ -19,11 +19,8 @@
 import com.google.common.annotations.VisibleForTesting;
 
 import android.app.Activity;
-import android.content.ContentUris;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.ContentResolver;
-import android.content.Context;
 import android.content.Intent;
 import android.database.ContentObserver;
 import android.database.Cursor;
@@ -33,24 +30,19 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.PowerManager;
-import android.provider.CallLog;
 import android.provider.VoicemailContract;
-import android.support.annotation.Nullable;
+import android.support.v4.content.FileProvider;
 import android.util.Log;
 import android.view.WindowManager.LayoutParams;
 
+import com.android.dialer.R;
 import com.android.dialer.calllog.CallLogAsyncTaskUtil;
-import com.android.dialer.calllog.CallLogQuery;
-import com.android.dialer.database.VoicemailArchiveContract;
 import com.android.dialer.util.AsyncTaskExecutor;
 import com.android.dialer.util.AsyncTaskExecutors;
 import com.android.common.io.MoreCloseables;
-import com.android.dialer.util.TelecomUtil;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.io.ByteStreams;
+
+import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executors;
@@ -140,6 +132,7 @@
             VoicemailPlaybackPresenter.class.getName() + ".IS_SPEAKER_PHONE_ON";
     public static final int PLAYBACK_REQUEST = 0;
     public static final int ARCHIVE_REQUEST = 1;
+    public static final int SHARE_REQUEST = 2;
 
     /**
      * The most recently cached duration. We cache this since we don't want to keep requesting it
@@ -181,6 +174,7 @@
     private VoicemailAudioManager mVoicemailAudioManager;
 
     private OnVoicemailDeletedListener mOnVoicemailDeletedListener;
+    private final VoicemailAsyncTaskUtil mVoicemailAsyncTaskUtil;
 
     /**
      * Obtain singleton instance of this class. Use a single instance to provide a consistent
@@ -208,6 +202,7 @@
         Context context = activity.getApplicationContext();
         mAsyncTaskExecutor = AsyncTaskExecutors.createAsyncTaskExecutor();
         mVoicemailAudioManager = new VoicemailAudioManager(context, this);
+        mVoicemailAsyncTaskUtil = new VoicemailAsyncTaskUtil(context.getContentResolver());
         PowerManager powerManager =
                 (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         if (powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
@@ -493,7 +488,6 @@
         private final Handler mFetchResultHandler;
         private final Uri mVoicemailUri;
         private final int mRequestCode;
-        private Uri mArchivedVoicemailUri;
 
         public FetchResultHandler(Handler handler, Uri uri, int code) {
             super(handler);
@@ -515,11 +509,7 @@
             if (mIsWaitingForResult.getAndSet(false) && mContext != null) {
                 mContext.getContentResolver().unregisterContentObserver(this);
                 if (mView != null) {
-                    if (mRequestCode == ARCHIVE_REQUEST) {
-                        notifyUiOfArchiveResult(mVoicemailUri, false);
-                    } else {
-                        mView.setFetchContentTimeout();
-                    }
+                    mView.setFetchContentTimeout();
                 }
             }
         }
@@ -538,13 +528,7 @@
 
                 @Override
                 public Boolean doInBackground(Void... params) {
-                    boolean hasContent = queryHasContent(mVoicemailUri);
-                    if (hasContent && mRequestCode == ARCHIVE_REQUEST) {
-                        mArchivedVoicemailUri =
-                                performArchiveVoicemailOnBackgroundThread(mVoicemailUri, true);
-                        return mArchivedVoicemailUri != null;
-                    }
-                    return hasContent;
+                    return queryHasContent(mVoicemailUri);
                 }
 
                 @Override
@@ -552,11 +536,11 @@
                     if (hasContent && mContext != null && mIsWaitingForResult.getAndSet(false)) {
                         mContext.getContentResolver().unregisterContentObserver(
                                 FetchResultHandler.this);
-                        switch (mRequestCode) {
-                            case ARCHIVE_REQUEST:
-                                notifyUiOfArchiveResult(mVoicemailUri, true);
-                            default:
-                                prepareContent();
+                        prepareContent();
+                        if (mRequestCode == ARCHIVE_REQUEST) {
+                            startArchiveVoicemailTask(mVoicemailUri, true /* archivedByUser */);
+                        } else if (mRequestCode == SHARE_REQUEST) {
+                            startArchiveVoicemailTask(mVoicemailUri, false /* archivedByUser */);
                         }
                     }
                 }
@@ -922,148 +906,89 @@
      * If voicemail has already been downloaded, go straight to archiving. Otherwise, request
      * the voicemail content first.
      */
-    public void archiveContent(Uri voicemailUri, boolean archivedByUser) {
-        if (!mIsPrepared) {
-            requestContent(ARCHIVE_REQUEST);
-        } else {
-            startArchiveVoicemailTask(voicemailUri, archivedByUser);
-        }
-    }
-
-    /**
-     * Asynchronous task used to archive a voicemail given its uri.
-     */
-    private void startArchiveVoicemailTask(final Uri voicemailUri, final boolean archivedByUser) {
-        mAsyncTaskExecutor.submit(Tasks.ARCHIVE_VOICEMAIL, new AsyncTask<Void, Void, Uri>() {
+    public void archiveContent(final Uri voicemailUri, final boolean archivedByUser) {
+        checkForContent(new OnContentCheckedListener() {
             @Override
-            public Uri doInBackground(Void... params) {
-                return performArchiveVoicemailOnBackgroundThread(voicemailUri, archivedByUser);
-            }
-
-            @Override
-            public void onPostExecute(Uri archivedVoicemailUri) {
-                notifyUiOfArchiveResult(voicemailUri, archivedVoicemailUri != null);
+            public void onContentChecked(boolean hasContent) {
+                if (!hasContent) {
+                    requestContent(archivedByUser ? ARCHIVE_REQUEST : SHARE_REQUEST);
+                } else {
+                    startArchiveVoicemailTask(voicemailUri, archivedByUser);
+                }
             }
         });
     }
 
     /**
-     * Copy the voicemail information to the local dialer database, and copy
-     * the voicemail content to a local file in the dialer application's
-     * internal storage (voicemails directory).
-     *
-     * @param voicemailUri the uri of the voicemail to archive
-     * @return If archive was successful, archived voicemail URI, otherwise null.
+     * Asynchronous task used to archive a voicemail given its uri.
      */
-    private Uri performArchiveVoicemailOnBackgroundThread(Uri voicemailUri,
-                                                          boolean archivedByUser) {
-        Cursor callLogInfo = mContext.getContentResolver().query(
-                ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
-                        ContentUris.parseId(mVoicemailUri)),
-                CallLogQuery._PROJECTION, null, null, null);
-        Cursor contentInfo = mContext.getContentResolver().query(
-                voicemailUri, null, null, null, null);
+    protected void startArchiveVoicemailTask(final Uri voicemailUri, final boolean archivedByUser) {
+        mVoicemailAsyncTaskUtil.archiveVoicemailContent(
+                new VoicemailAsyncTaskUtil.OnArchiveVoicemailListener() {
+                    @Override
+                    public void onArchiveVoicemail(final Uri archivedVoicemailUri) {
+                        if (archivedVoicemailUri == null) {
+                            notifyUiOfArchiveResult(voicemailUri, false);
+                            return;
+                        }
 
-        if (callLogInfo == null || contentInfo == null) {
-            return null;
-        }
+                        if (archivedByUser) {
+                            setArchivedVoicemailStatusAndUpdateUI(voicemailUri,
+                                    archivedVoicemailUri, true);
+                        } else {
+                            sendShareIntent(archivedVoicemailUri);
+                        }
+                    }
+                }, voicemailUri);
+    }
 
-        callLogInfo.moveToFirst();
-        contentInfo.moveToFirst();
+    /**
+     * Sends the intent for sharing the voicemail file.
+     */
+    protected void sendShareIntent(final Uri voicemailUri) {
+        mVoicemailAsyncTaskUtil.getVoicemailFilePath(
+                new VoicemailAsyncTaskUtil.OnGetArchivedVoicemailFilePathListener() {
+                    @Override
+                    public void onGetArchivedVoicemailFilePath(String filePath) {
+                        mView.enableUiElements();
+                        if (filePath == null) {
+                            mView.setFetchContentTimeout();
+                            return;
+                        }
+                        Uri voicemailFileUri = FileProvider.getUriForFile(
+                                mContext,
+                                mContext.getString(R.string.contacts_file_provider_authority),
+                                new File(filePath));
+                        mContext.startActivity(Intent.createChooser(
+                                getShareIntent(voicemailFileUri),
+                                mContext.getResources().getText(
+                                        R.string.call_log_share_voicemail)));
+                    }
+                }, voicemailUri);
+    }
 
-        // Create values to insert into database
-        ContentValues values = new ContentValues();
-        values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER,
-                contentInfo.getString(contentInfo.getColumnIndex(
-                        VoicemailContract.Voicemails.NUMBER)));
+    /** Sets archived_by_user field to the given boolean and updates the URI. */
+    private void setArchivedVoicemailStatusAndUpdateUI(
+            final Uri voicemailUri,
+            final Uri archivedVoicemailUri,
+            boolean status) {
+        mVoicemailAsyncTaskUtil.setVoicemailArchiveStatus(
+                new VoicemailAsyncTaskUtil.OnSetVoicemailArchiveStatusListener() {
+                    @Override
+                    public void onSetVoicemailArchiveStatus(boolean success) {
+                        notifyUiOfArchiveResult(voicemailUri, success);
+                    }
+                }, archivedVoicemailUri, status);
+    }
 
-        values.put(VoicemailArchiveContract.VoicemailArchive.DATE,
-                contentInfo.getLong(contentInfo.getColumnIndex(
-                        VoicemailContract.Voicemails.DATE)));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.DURATION,
-                contentInfo.getLong(contentInfo.getColumnIndex(
-                        VoicemailContract.Voicemails.DURATION)));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.MIME_TYPE,
-                contentInfo.getString(contentInfo.getColumnIndex(
-                        VoicemailContract.Voicemails.MIME_TYPE)));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.COUNTRY_ISO,
-                callLogInfo.getString(CallLogQuery.COUNTRY_ISO));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.GEOCODED_LOCATION,
-                callLogInfo.getString(CallLogQuery.GEOCODED_LOCATION));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NAME,
-                callLogInfo.getString(CallLogQuery.CACHED_NAME));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_TYPE,
-                callLogInfo.getInt(CallLogQuery.CACHED_NUMBER_TYPE));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_LABEL,
-                callLogInfo.getString(CallLogQuery.CACHED_NUMBER_LABEL));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_LOOKUP_URI,
-                callLogInfo.getString(CallLogQuery.CACHED_LOOKUP_URI));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_MATCHED_NUMBER,
-                callLogInfo.getString(CallLogQuery.CACHED_MATCHED_NUMBER));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NORMALIZED_NUMBER,
-                callLogInfo.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_FORMATTED_NUMBER,
-                callLogInfo.getString(CallLogQuery.CACHED_FORMATTED_NUMBER));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.ARCHIVED, archivedByUser);
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER_PRESENTATION,
-                callLogInfo.getInt(CallLogQuery.NUMBER_PRESENTATION));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_COMPONENT_NAME,
-                callLogInfo.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_ID,
-                callLogInfo.getString(CallLogQuery.ACCOUNT_ID));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.FEATURES,
-                callLogInfo.getInt(CallLogQuery.FEATURES));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.SERVER_ID,
-                contentInfo.getInt(contentInfo.getColumnIndex(
-                        VoicemailContract.Voicemails._ID)));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.TRANSCRIPTION,
-                contentInfo.getString(contentInfo.getColumnIndex(
-                        VoicemailContract.Voicemails.TRANSCRIPTION)));
-
-        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_PHOTO_URI,
-                callLogInfo.getLong(CallLogQuery.CACHED_PHOTO_URI));
-
-        callLogInfo.close();
-        contentInfo.close();
-
-        // Insert info into dialer database
-        Uri archivedVoicemailUri = mContext.getContentResolver().insert(
-                        VoicemailArchiveContract.VoicemailArchive.CONTENT_URI, values);
-        try {
-            // Copy voicemail content to a local file
-            InputStream inputStream = mContext.getContentResolver()
-                    .openInputStream(voicemailUri);
-            OutputStream outputStream = mContext.getContentResolver()
-                    .openOutputStream(archivedVoicemailUri);
-
-            ByteStreams.copy(inputStream, outputStream);
-            inputStream.close();
-            outputStream.close();
-        } catch (IOException e) {
-            // Roll back insert if new file creation failed
-            mContext.getContentResolver().delete(archivedVoicemailUri, null, null);
-            Log.w(TAG, "Failed to copy voicemail content to temporary file");
-            return null;
-        }
-        return archivedVoicemailUri;
+    private Intent getShareIntent(Uri voicemailFileUri) {
+        Intent shareIntent = new Intent();
+        shareIntent.setAction(Intent.ACTION_SEND);
+        shareIntent.putExtra(Intent.EXTRA_STREAM, voicemailFileUri);
+        shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        shareIntent.setType(mContext.getContentResolver()
+                .getType(voicemailFileUri));
+        return shareIntent;
     }
 
     @VisibleForTesting
diff --git a/tests/src/com/android/dialer/database/VoicemailArchiveProviderTest.java b/tests/src/com/android/dialer/database/VoicemailArchiveProviderTest.java
index cec7e05..abc2dc5 100644
--- a/tests/src/com/android/dialer/database/VoicemailArchiveProviderTest.java
+++ b/tests/src/com/android/dialer/database/VoicemailArchiveProviderTest.java
@@ -79,6 +79,33 @@
         assertTrue(doesFileExist());
     }
 
+    public void testQuery_createFileMimeTypeIsAMR() throws Exception {
+        insertVoicemailWithMimeType("audio/amr");
+        assertTrue(doesRowExist());
+        assertFalse(doesFileExist());
+        createFile();
+        assertTrue(doesFileExist());
+        assertEquals("amr", getFileExtension(getFilePath()));
+    }
+
+    public void testQuery_createFileMimeTypeIsMP3() throws Exception {
+        insertVoicemailWithMimeType("audio/mpeg");
+        assertTrue(doesRowExist());
+        assertFalse(doesFileExist());
+        createFile();
+        assertTrue(doesFileExist());
+        assertEquals("mp3", getFileExtension(getFilePath()));
+    }
+
+    public void testQuery_createFileMimeTypeNotExists() throws Exception {
+        insertVoicemailWithMimeType(TEST_STRING);
+        assertTrue(doesRowExist());
+        assertFalse(doesFileExist());
+        createFile();
+        assertTrue(doesFileExist());
+        assertEquals("", getFileExtension(getFilePath()));
+    }
+
     public void testQuery() {
         insertVoicemail();
         updateCursor();
@@ -179,6 +206,13 @@
         return voicemailFile.exists();
     }
 
+    private static String getFileExtension(String filePath) {
+        File file = new File(filePath);
+        String fileName = file.getName();
+        int index = fileName.lastIndexOf(".");
+        return index > 0 ? fileName.substring(index + 1) : "";
+    }
+
     private void assertCursorCount(int count) {
         assertEquals(count, mCursor.getCount());
     }
@@ -196,6 +230,12 @@
         mVoicemailUri = mResolver.insert(VoicemailArchive.CONTENT_URI, getTestValues());
     }
 
+    private void insertVoicemailWithMimeType(String mimeType) {
+        ContentValues values = getTestValues();
+        values.put(VoicemailArchive.MIME_TYPE, mimeType);
+        mVoicemailUri = mResolver.insert(VoicemailArchive.CONTENT_URI, values);
+    }
+
     private void updateCursor() {
         mCursor = mResolver.query(mVoicemailUri, null, null, null, null);
         assertEquals(true, mCursor.getCount() > 0);
@@ -226,7 +266,7 @@
         return mCursor.getString(mCursor.getColumnIndex(VoicemailArchive._DATA));
     }
 
-    private ContentValues getTestValues() {
+    private static ContentValues getTestValues() {
         ContentValues values = new ContentValues();
         values.put(VoicemailArchive.NUMBER, TEST_NUMBER);
         values.put(VoicemailArchive.MIME_TYPE, TEST_MIME_TYPE);
diff --git a/tests/src/com/android/dialer/voicemail/VoicemailActivityInstrumentationTestCase2.java b/tests/src/com/android/dialer/voicemail/VoicemailActivityInstrumentationTestCase2.java
index a992e8b..cabaf67 100644
--- a/tests/src/com/android/dialer/voicemail/VoicemailActivityInstrumentationTestCase2.java
+++ b/tests/src/com/android/dialer/voicemail/VoicemailActivityInstrumentationTestCase2.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2016 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.dialer.voicemail;
 
 import android.app.Activity;
@@ -23,7 +39,6 @@
 
 import static com.android.dialer.voicemail.VoicemailPlaybackPresenter.Tasks.CHECK_FOR_CONTENT;
 
-
 /**
  * Common methods and attributes between {@link VoicemailArchiveTest} and
  * {@link VoicemailPlaybackTest}.
diff --git a/tests/src/com/android/dialer/voicemail/VoicemailArchiveTest.java b/tests/src/com/android/dialer/voicemail/VoicemailArchiveTest.java
index 8cbd344..bbd5edc 100644
--- a/tests/src/com/android/dialer/voicemail/VoicemailArchiveTest.java
+++ b/tests/src/com/android/dialer/voicemail/VoicemailArchiveTest.java
@@ -15,14 +15,12 @@
  */
 
 package com.android.dialer.voicemail;
+
 import android.content.ContentUris;
 import android.content.ContentValues;
-
-import com.android.dialer.R;
-import com.android.dialer.database.VoicemailArchiveContract.VoicemailArchive;
-
 import android.content.res.AssetManager;
-import android.test.suitebuilder.annotation.Suppress;
+
+import com.android.dialer.database.VoicemailArchiveContract.VoicemailArchive;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/tests/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtilTest.java b/tests/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtilTest.java
new file mode 100644
index 0000000..d98d9fa
--- /dev/null
+++ b/tests/src/com/android/dialer/voicemail/VoicemailAsyncTaskUtilTest.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2016 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.dialer.voicemail;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.provider.CallLog;
+import android.provider.VoicemailContract;
+import android.test.InstrumentationTestCase;
+import android.test.mock.MockContentResolver;
+
+import com.android.contacts.common.test.mocks.MockContentProvider;
+import com.android.contacts.common.test.mocks.MockContentProvider.Query;
+import com.android.dialer.calllog.CallLogQuery;
+import com.android.dialer.database.VoicemailArchiveContract;
+import com.android.dialer.util.AsyncTaskExecutors;
+import com.android.dialer.util.FakeAsyncTaskExecutor;
+import com.android.dialer.voicemail.VoicemailAsyncTaskUtil.OnGetArchivedVoicemailFilePathListener;
+import com.android.dialer.voicemail.VoicemailAsyncTaskUtil.OnArchiveVoicemailListener;
+import com.android.dialer.voicemail.VoicemailAsyncTaskUtil.OnSetVoicemailArchiveStatusListener;
+
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import static org.mockito.Mockito.*;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class VoicemailAsyncTaskUtilTest extends InstrumentationTestCase {
+    private static final String TEST_MIME_TYPE = "audio/mp3";
+    private static final String TEST_NUMBER = "+1412555555";
+    private static final String TEST_FILE_PATH = "TEST_PATH";
+    private static final String TEST_TRANSCRIPTION = "TEST_TRANSCRIPTION";
+    private static final long TEST_DATE = 0;
+    private static final long TEST_DURATION = 0;
+    private static final int TEST_SERVER_ID = 1;
+    private static final int TEST_ID = 2;
+    private static final Uri ARCHIVED_VOICEMAIL_URI =
+            VoicemailArchiveContract.VoicemailArchive.buildWithId(TEST_ID);
+    private static final Uri VOICEMAIL_URI = VoicemailContract.Voicemails.CONTENT_URI
+            .buildUpon().appendPath(Integer.toString(TEST_SERVER_ID)).build();
+    private static final String[] CALLLOG_QUERY_PROJECTION = new String[] {
+            CallLog.Calls._ID,
+            CallLog.Calls.NUMBER,
+            CallLog.Calls.DATE,
+            CallLog.Calls.DURATION,
+            CallLog.Calls.TYPE,
+            CallLog.Calls.COUNTRY_ISO,
+            CallLog.Calls.VOICEMAIL_URI,
+            CallLog.Calls.GEOCODED_LOCATION,
+            CallLog.Calls.CACHED_NAME,
+            CallLog.Calls.CACHED_NUMBER_TYPE,
+            CallLog.Calls.CACHED_NUMBER_LABEL,
+            CallLog.Calls.CACHED_LOOKUP_URI,
+            CallLog.Calls.CACHED_MATCHED_NUMBER,
+            CallLog.Calls.CACHED_NORMALIZED_NUMBER,
+            CallLog.Calls.CACHED_PHOTO_ID,
+            CallLog.Calls.CACHED_FORMATTED_NUMBER,
+            CallLog.Calls.IS_READ,
+            CallLog.Calls.NUMBER_PRESENTATION,
+            CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME,
+            CallLog.Calls.PHONE_ACCOUNT_ID,
+            CallLog.Calls.FEATURES,
+            CallLog.Calls.DATA_USAGE,
+            CallLog.Calls.TRANSCRIPTION,
+            CallLog.Calls.CACHED_PHOTO_URI
+    };
+    private static final String[] VOICEMAIL_PROJECTION = new String[] {
+            VoicemailContract.Voicemails._ID,
+            VoicemailContract.Voicemails.NUMBER,
+            VoicemailContract.Voicemails.DATE,
+            VoicemailContract.Voicemails.DURATION,
+            VoicemailContract.Voicemails.MIME_TYPE,
+            VoicemailContract.Voicemails.TRANSCRIPTION,
+    };
+
+    private final MockContentResolver mContentResolver = new MockContentResolver();
+    private final MockContentProvider mArchiveContentProvider = new TestVoicemailContentProvider();
+    private final MockContentProvider mVoicemailContentProvider =
+            new TestVoicemailContentProvider();
+    private final MockContentProvider mCallLogContentProvider = new MockContentProvider();
+    @Rule
+    private final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    private VoicemailAsyncTaskUtil mVoicemailAsyncTaskUtil;
+    private FakeAsyncTaskExecutor mFakeAsyncTaskExecutor;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mFakeAsyncTaskExecutor = new FakeAsyncTaskExecutor(getInstrumentation());
+        AsyncTaskExecutors.setFactoryForTest(mFakeAsyncTaskExecutor.getFactory());
+        mContentResolver.addProvider(VoicemailArchiveContract.AUTHORITY, mArchiveContentProvider);
+        mContentResolver.addProvider(VoicemailContract.AUTHORITY, mVoicemailContentProvider);
+        mContentResolver.addProvider(CallLog.AUTHORITY, mCallLogContentProvider);
+        mVoicemailAsyncTaskUtil = new VoicemailAsyncTaskUtil(mContentResolver);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        AsyncTaskExecutors.setFactoryForTest(null);
+        super.tearDown();
+    }
+
+    public void testGetVoicemailFilePath_VoicemailExists() throws Throwable {
+        newVoicemailArchiveQuery().withAnyProjection()
+                .returnRow(getCombinedContentValuesWithData());
+        final OnGetArchivedVoicemailFilePathListener listener =
+                mock(OnGetArchivedVoicemailFilePathListener.class);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mVoicemailAsyncTaskUtil.getVoicemailFilePath(listener, ARCHIVED_VOICEMAIL_URI);
+            }
+        });
+        mFakeAsyncTaskExecutor.runTask(VoicemailAsyncTaskUtil.Tasks.GET_VOICEMAIL_FILE_PATH);
+        verify(listener).onGetArchivedVoicemailFilePath(TEST_FILE_PATH);
+        verifyQueries();
+    }
+
+    public void testGetVoicemailFilePath_VoicemailNotExists() throws Throwable {
+        newVoicemailArchiveFakeQuery().withAnyProjection().returnEmptyCursor();
+        final OnGetArchivedVoicemailFilePathListener listener =
+                mock(OnGetArchivedVoicemailFilePathListener.class);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mVoicemailAsyncTaskUtil.getVoicemailFilePath(listener, getFakeVoicemailUri());
+            }
+        });
+        mFakeAsyncTaskExecutor.runTask(VoicemailAsyncTaskUtil.Tasks.GET_VOICEMAIL_FILE_PATH);
+        verify(listener).onGetArchivedVoicemailFilePath(null);
+        verifyQueries();
+    }
+
+    public void testSetVoicemailArchiveStatus_VoicemailNotExists() throws Throwable {
+        newVoicemailArchiveNotExistsUpdateQuery(true);
+        final OnSetVoicemailArchiveStatusListener listener =
+                mock(OnSetVoicemailArchiveStatusListener.class);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mVoicemailAsyncTaskUtil.setVoicemailArchiveStatus(listener,
+                        getFakeVoicemailUri(), true);
+            }
+        });
+        mFakeAsyncTaskExecutor.runTask(VoicemailAsyncTaskUtil.Tasks.SET_VOICEMAIL_ARCHIVE_STATUS);
+        verify(listener).onSetVoicemailArchiveStatus(false);
+        verifyQueries();
+    }
+
+    public void testSetVoicemailArchiveStatus_VoicemailExists() throws Throwable {
+        newVoicemailArchiveExistsUpdateQuery(true);
+        final OnSetVoicemailArchiveStatusListener listener =
+                mock(OnSetVoicemailArchiveStatusListener.class);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mVoicemailAsyncTaskUtil.setVoicemailArchiveStatus(listener,
+                        ARCHIVED_VOICEMAIL_URI, true);
+            }
+        });
+        mFakeAsyncTaskExecutor.runTask(VoicemailAsyncTaskUtil.Tasks.SET_VOICEMAIL_ARCHIVE_STATUS);
+        verify(listener).onSetVoicemailArchiveStatus(true);
+        verifyQueries();
+    }
+
+    public void testArchiveVoicemailContent_ArchiveNotExists() throws Throwable {
+        newVoicemailArchiveExistsQuery().returnEmptyCursor();
+        newQueryCallLogInfo().returnRow(getCallLogContentValues());
+        newVoicemailQuery().returnRow(getVoicemailContentValues());
+        newVoicemailArchiveInsert();
+        final OnArchiveVoicemailListener listener = mock(OnArchiveVoicemailListener.class);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mVoicemailAsyncTaskUtil.archiveVoicemailContent(listener, VOICEMAIL_URI);
+            }
+        });
+        mFakeAsyncTaskExecutor.runTask(VoicemailAsyncTaskUtil.Tasks.ARCHIVE_VOICEMAIL_CONTENT);
+        verify(listener).onArchiveVoicemail(ARCHIVED_VOICEMAIL_URI);
+        verifyQueries();
+    }
+
+    public void testArchiveVoicemailContent_ArchiveExists() throws Throwable {
+        newVoicemailArchiveExistsQuery().returnRow(getCombinedValuesWithId());
+        final OnArchiveVoicemailListener listener = mock(OnArchiveVoicemailListener.class);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mVoicemailAsyncTaskUtil.archiveVoicemailContent(listener, VOICEMAIL_URI);
+            }
+        });
+        mFakeAsyncTaskExecutor.runTask(VoicemailAsyncTaskUtil.Tasks.ARCHIVE_VOICEMAIL_CONTENT);
+        verify(listener).onArchiveVoicemail(ARCHIVED_VOICEMAIL_URI);
+        verifyQueries();
+    }
+
+    public void testArchiveVoicemailContent_CallLogInfoNotExists() throws Throwable {
+        newVoicemailArchiveExistsQuery().returnEmptyCursor();
+        newQueryCallLogInfo().returnEmptyCursor();
+        newVoicemailQuery().returnEmptyCursor();
+        final OnArchiveVoicemailListener listener = mock(OnArchiveVoicemailListener.class);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mVoicemailAsyncTaskUtil.archiveVoicemailContent(listener, VOICEMAIL_URI);
+            }
+        });
+        mFakeAsyncTaskExecutor.runTask(VoicemailAsyncTaskUtil.Tasks.ARCHIVE_VOICEMAIL_CONTENT);
+        verify(listener).onArchiveVoicemail(null);
+        verifyQueries();
+    }
+
+    private Query newVoicemailArchiveQuery() {
+        return mArchiveContentProvider.expectQuery(ARCHIVED_VOICEMAIL_URI);
+    }
+
+    private Query newVoicemailArchiveFakeQuery() {
+        return mArchiveContentProvider.expectQuery(getFakeVoicemailUri());
+    }
+
+    private Query newQueryCallLogInfo() {
+        return mCallLogContentProvider.expectQuery(ContentUris.withAppendedId(
+                CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL, ContentUris.parseId(VOICEMAIL_URI)))
+                .withProjection(CALLLOG_QUERY_PROJECTION);
+    }
+
+    private Query newVoicemailQuery() {
+        return mVoicemailContentProvider.expectQuery(VOICEMAIL_URI).withAnySelection()
+                .withProjection(VOICEMAIL_PROJECTION);
+    }
+
+    private Query newVoicemailArchiveExistsQuery() {
+        return mArchiveContentProvider.expectQuery(
+                VoicemailArchiveContract.VoicemailArchive.CONTENT_URI)
+                .withSelection(VoicemailArchiveContract.VoicemailArchive.SERVER_ID + "="
+                        + ContentUris.parseId(VOICEMAIL_URI), (String[]) null)
+                .withProjection(VoicemailArchiveContract.VoicemailArchive._ID);
+    }
+
+    private void newVoicemailArchiveInsert() {
+        mArchiveContentProvider.expectInsert(
+                VoicemailArchiveContract.VoicemailArchive.CONTENT_URI, getCombinedContentValues(),
+                ARCHIVED_VOICEMAIL_URI);
+    }
+
+    private void newVoicemailArchiveNotExistsUpdateQuery(boolean status) {
+        mArchiveContentProvider.expectUpdate(getFakeVoicemailUri(),
+                getArchiveStatusUpdateValues(status), null, null).returnRowsAffected(0);
+    }
+
+    private void newVoicemailArchiveExistsUpdateQuery(boolean status) {
+        mArchiveContentProvider.expectUpdate(ARCHIVED_VOICEMAIL_URI,
+                getArchiveStatusUpdateValues(status), null, null).returnRowsAffected(1);
+    }
+
+    private static ContentValues getCallLogContentValues() {
+        ContentValues values = new ContentValues();
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.GEOCODED_LOCATION], "");
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.CACHED_NAME], "");
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.COUNTRY_ISO], "");
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.CACHED_NUMBER_TYPE], 0);
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.CACHED_NUMBER_LABEL], "");
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.CACHED_LOOKUP_URI], "");
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.CACHED_MATCHED_NUMBER], "");
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.CACHED_NORMALIZED_NUMBER], "");
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.CACHED_FORMATTED_NUMBER], "");
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.NUMBER_PRESENTATION], 0);
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.ACCOUNT_COMPONENT_NAME], "");
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.ACCOUNT_ID], "");
+        values.put(CALLLOG_QUERY_PROJECTION[CallLogQuery.FEATURES], 0);
+        values.put(CALLLOG_QUERY_PROJECTION[23], "");
+        return values;
+    }
+
+    private static ContentValues getVoicemailContentValues() {
+        ContentValues values = new ContentValues();
+        values.put(VoicemailContract.Voicemails.NUMBER, TEST_NUMBER);
+        values.put(VoicemailContract.Voicemails.DATE, TEST_DATE);
+        values.put(VoicemailContract.Voicemails.DURATION, TEST_DURATION);
+        values.put(VoicemailContract.Voicemails.MIME_TYPE, TEST_MIME_TYPE);
+        values.put(VoicemailContract.Voicemails._ID, TEST_SERVER_ID);
+        values.put(VoicemailContract.Voicemails.TRANSCRIPTION, TEST_TRANSCRIPTION);
+        values.put("_data", TEST_FILE_PATH);    // VoicemailContract.Voicemails._DATA
+        return values;
+    }
+
+    private static ContentValues getCombinedContentValues() {
+        ContentValues values = new ContentValues();
+        // Call log info
+        values.put(VoicemailArchiveContract.VoicemailArchive.GEOCODED_LOCATION, "");
+        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NAME, "");
+        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_TYPE, 0);
+        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NUMBER_LABEL, "");
+        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_LOOKUP_URI, "");
+        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_MATCHED_NUMBER, "");
+        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_NORMALIZED_NUMBER, "");
+        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_FORMATTED_NUMBER, "");
+        values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER_PRESENTATION, 0);
+        values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_COMPONENT_NAME, "");
+        values.put(VoicemailArchiveContract.VoicemailArchive.ACCOUNT_ID, "");
+        values.put(VoicemailArchiveContract.VoicemailArchive.FEATURES, 0);
+        values.put(VoicemailArchiveContract.VoicemailArchive.CACHED_PHOTO_URI, "");
+        values.put(VoicemailArchiveContract.VoicemailArchive.COUNTRY_ISO, "");
+
+        // Voicemail content info
+        values.put(VoicemailArchiveContract.VoicemailArchive.NUMBER, TEST_NUMBER);
+        values.put(VoicemailArchiveContract.VoicemailArchive.DATE, TEST_DATE);
+        values.put(VoicemailArchiveContract.VoicemailArchive.DURATION, TEST_DURATION);
+        values.put(VoicemailArchiveContract.VoicemailArchive.MIME_TYPE, TEST_MIME_TYPE);
+        values.put(VoicemailArchiveContract.VoicemailArchive.SERVER_ID, TEST_SERVER_ID);
+        values.put(VoicemailArchiveContract.VoicemailArchive.TRANSCRIPTION, TEST_TRANSCRIPTION);
+
+        // Custom fields
+        values.put(VoicemailArchiveContract.VoicemailArchive.ARCHIVED, false);
+        return values;
+    }
+
+    private static ContentValues getCombinedContentValuesWithData() {
+        ContentValues values = getCombinedContentValues();
+        values.put(VoicemailArchiveContract.VoicemailArchive._DATA, TEST_FILE_PATH);
+        return values;
+    }
+
+    private static ContentValues getCombinedValuesWithId() {
+        ContentValues values = getCombinedContentValuesWithData();
+        values.put(VoicemailArchiveContract.VoicemailArchive._ID, TEST_ID);
+        return values;
+    }
+
+    private static ContentValues getArchiveStatusUpdateValues(boolean status) {
+        ContentValues values = new ContentValues();
+        values.put(VoicemailArchiveContract.VoicemailArchive.ARCHIVED, status);
+        return values;
+    }
+
+    private static Uri getFakeVoicemailUri() {
+        return VoicemailArchiveContract.VoicemailArchive.buildWithId(0);
+    }
+
+    private void verifyQueries() {
+        mArchiveContentProvider.verify();
+        mCallLogContentProvider.verify();
+        mVoicemailContentProvider.verify();
+    }
+
+    private class TestVoicemailContentProvider extends MockContentProvider {
+        @Override
+        public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+            int modeBits = ParcelFileDescriptor.parseMode(mode);
+            try {
+                return ParcelFileDescriptor.open(mTemporaryFolder.newFile(), modeBits);
+            } catch (IOException e) {
+                return null;
+            }
+        }
+
+        @Override
+        public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) {
+            try {
+                return new AssetFileDescriptor(openFile(uri, "r"), 0,
+                        AssetFileDescriptor.UNKNOWN_LENGTH);
+            } catch (FileNotFoundException e) {
+                return null;
+            }
+        }
+    }
+}
+
diff --git a/tests/src/com/android/dialer/voicemail/VoicemailPlaybackTest.java b/tests/src/com/android/dialer/voicemail/VoicemailPlaybackTest.java
index abd582b..be9905e 100644
--- a/tests/src/com/android/dialer/voicemail/VoicemailPlaybackTest.java
+++ b/tests/src/com/android/dialer/voicemail/VoicemailPlaybackTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -16,10 +16,14 @@
 
 package com.android.dialer.voicemail;
 
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.net.Uri;
 import android.test.suitebuilder.annotation.Suppress;
 
 import com.android.dialer.calllog.CallLogActivity;
-
+import com.android.dialer.database.VoicemailArchiveContract;
+import static com.android.dialer.voicemail.VoicemailAsyncTaskUtil.Tasks.ARCHIVE_VOICEMAIL_CONTENT;
 import static com.android.dialer.voicemail.VoicemailPlaybackPresenter.Tasks.CHECK_FOR_CONTENT;
 
 /**
@@ -38,6 +42,12 @@
         mPresenter = VoicemailPlaybackPresenter.getInstance(getActivity(), null);
     }
 
+    @Override
+    public void tearDown() throws Exception {
+        cleanUpArchivedVoicemailUri();
+        super.tearDown();
+    }
+
     @Suppress
     public void testWhenCheckForContentCompletes() throws Throwable {
         setUriForRealFileVoicemailEntry();
@@ -54,4 +64,82 @@
 
         assertStateTextContains("Loading voicemail");
     }
+
+    public void testArchiveContent() throws Throwable {
+        setUriForRealFileVoicemailEntry();
+        setPlaybackViewForPresenter();
+        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mPresenter.archiveContent(mVoicemailUri, true);
+            }
+        });
+        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+        mFakeAsyncTaskExecutor.runTask(ARCHIVE_VOICEMAIL_CONTENT);
+        getInstrumentation().waitForIdleSync();
+        assertVoicemailArchived();
+    }
+
+    public void testShareContent() throws Throwable {
+        setUriForRealFileVoicemailEntry();
+        setPlaybackViewForPresenter();
+        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mPresenter.archiveContent(mVoicemailUri, false);
+            }
+        });
+        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+        mFakeAsyncTaskExecutor.runTask(ARCHIVE_VOICEMAIL_CONTENT);
+        getInstrumentation().waitForIdleSync();
+        assertVoicemailArchived();
+    }
+
+    private void assertVoicemailArchived() {
+        try (Cursor cursor = getArchivedVoicemailCursor()) {
+            assertTrue(hasContent(cursor));
+            assertEquals(ContentUris.parseId(mVoicemailUri), getRowServerId(cursor));
+        } catch (Exception e) {
+            fail("Voicemail was not archived: " + e.toString());
+        }
+    }
+
+    private void cleanUpArchivedVoicemailUri() {
+        try (Cursor cursor = getArchivedVoicemailCursor()) {
+            if (hasContent(cursor)) {
+                getContentResolver().delete(getRowUri(cursor), null, null);
+            }
+        }
+    }
+
+    private Cursor getArchivedVoicemailCursor() {
+        return getContentResolver().query(
+                VoicemailArchiveContract.VoicemailArchive.CONTENT_URI,
+                new String[] {
+                        VoicemailArchiveContract.VoicemailArchive._ID,
+                        VoicemailArchiveContract.VoicemailArchive.SERVER_ID
+                },
+                VoicemailArchiveContract.VoicemailArchive.SERVER_ID + "="
+                        + ContentUris.parseId(mVoicemailUri),
+                null,
+                null);
+    }
+
+    private int getRowServerId(Cursor cursor) {
+        return cursor
+                .getInt(cursor.getColumnIndex(VoicemailArchiveContract.VoicemailArchive.SERVER_ID));
+    }
+
+    private Uri getRowUri(Cursor cursor) {
+        return VoicemailArchiveContract.VoicemailArchive.buildWithId(cursor.getInt(
+                cursor.getColumnIndex(VoicemailArchiveContract.VoicemailArchive._ID)));
+    }
+
+    private boolean hasContent(Cursor cursor) {
+        return cursor != null && cursor.moveToFirst();
+    }
 }
\ No newline at end of file