Merge "Fetch voicemail payload, which is usually the audio file."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 62bc4dc..ef7a812 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -624,9 +624,9 @@
             </intent-filter>
         </receiver>
         <service
-                android:name="com.android.phone.vvm.omtp.OmtpVvmSyncService"
-                android:exported="true"
-                android:process=":sync">
+            android:name="com.android.phone.vvm.omtp.OmtpVvmSyncService"
+            android:exported="true"
+            android:process=":sync">
             <intent-filter>
                 <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
@@ -635,18 +635,29 @@
         </service>
         <service
             android:name="android.telecom.AuthenticatorService">
-        <intent-filter>
-            <action android:name="android.accounts.AccountAuthenticator"/>
-        </intent-filter>
-        <meta-data
-            android:name="android.accounts.AccountAuthenticator"
-            android:resource="@xml/authenticator" />
+            <intent-filter>
+                <action android:name="android.accounts.AccountAuthenticator"/>
+            </intent-filter>
+            <meta-data
+                android:name="android.accounts.AccountAuthenticator"
+                android:resource="@xml/authenticator" />
        </service>
-        <receiver android:name="com.android.phone.vvm.omtp.SimChangeReceiver"
+       <receiver android:name="com.android.phone.vvm.omtp.SimChangeReceiver"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SIM_STATE_CHANGED" />
             </intent-filter>
-        </receiver>
+       </receiver>
+       <receiver
+           android:name="com.android.phone.vvm.omtp.sync.FetchVoicemailReceiver"
+           android:exported="true">
+           <intent-filter>
+              <action android:name="android.intent.action.FETCH_VOICEMAIL" />
+               <data
+                   android:scheme="content"
+                   android:host="com.android.voicemail"
+                   android:mimeType="vnd.android.cursor.item/voicemail" />
+          </intent-filter>
+       </receiver>
     </application>
 </manifest>
diff --git a/src/com/android/phone/common/mail/store/ImapFolder.java b/src/com/android/phone/common/mail/store/ImapFolder.java
index 5e9bc6b..faeb93f 100644
--- a/src/com/android/phone/common/mail/store/ImapFolder.java
+++ b/src/com/android/phone/common/mail/store/ImapFolder.java
@@ -92,7 +92,6 @@
      */
     public interface MessageRetrievalListener {
         public void messageRetrieved(Message message);
-        public void loadAttachmentProgress(int progress);
     }
 
     private void destroyResponses() {
@@ -457,14 +456,6 @@
             while (-1 != (n = in.read(buffer))) {
                 out.write(buffer, 0, n);
                 count += n;
-                if (listener != null) {
-                    if (size == 0) {
-                        // We don't know how big the file is, so just fake it.
-                        listener.loadAttachmentProgress((int)Math.ceil(100 * (1-1.0/count)));
-                    } else {
-                        listener.loadAttachmentProgress(count * 100 / size);
-                    }
-                }
             }
         } catch (Base64DataException bde) {
             String warning = "\n\n" + context.getString(R.string.message_decode_error);
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
index edc9bec..fc444ba 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
@@ -167,7 +167,8 @@
         public int markReadInDatabase(List<Voicemail> voicemails) {
             int count = voicemails.size();
             for (int i = 0; i < count; i++) {
-                Uri uri = ContentUris.withAppendedId(Voicemails.CONTENT_URI,
+                Uri uri = ContentUris.withAppendedId(
+                        VoicemailContract.Voicemails.buildSourceUri(mContext.getPackageName()),
                         voicemails.get(i).getId());
                 mContentResolver.update(uri, new ContentValues(), null, null);
             }
diff --git a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
index 22b919b..3a260d7 100644
--- a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
+++ b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
@@ -20,7 +20,10 @@
 import android.content.Context;
 import android.telecom.Voicemail;
 
+import android.util.Base64;
+
 import com.android.phone.common.mail.Address;
+import com.android.phone.common.mail.Body;
 import com.android.phone.common.mail.BodyPart;
 import com.android.phone.common.mail.FetchProfile;
 import com.android.phone.common.mail.Flag;
@@ -34,7 +37,13 @@
 import com.android.phone.common.mail.store.imap.ImapConstants;
 import com.android.phone.common.mail.utils.LogUtils;
 import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.phone.vvm.omtp.sync.VoicemailFetchedCallback;
 
+import libcore.io.IoUtils;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -63,7 +72,7 @@
             // TODO: determine the security protocol (e.g. ssl, tls, none, etc.)
             mImapStore = new ImapStore(
                     context, username, password, port, serverName,
-                    ImapStore.FLAG_TLS);
+                    ImapStore.FLAG_NONE);
         } catch (NumberFormatException e) {
             LogUtils.e(TAG, e, "Could not parse port number");
         }
@@ -160,6 +169,42 @@
         return listener.getVoicemail();
     }
 
+
+    public void fetchVoicemailPayload(VoicemailFetchedCallback callback, final String uid) {
+        Message message;
+        try {
+            mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE);
+            if (mFolder == null) {
+                // This means we were unable to successfully open the folder.
+                return;
+            }
+            message = mFolder.getMessage(uid);
+            VoicemailPayload voicemailPayload = fetchVoicemailPayload(message);
+            callback.setVoicemailContent(voicemailPayload);
+        } catch (MessagingException e) {
+        } finally {
+            closeImapFolder();
+        }
+    }
+
+    /**
+     * Fetches the body of the given message and returns the parsed voicemail payload.
+     *
+     * @throws MessagingException if fetching the body of the message fails
+     */
+    private VoicemailPayload fetchVoicemailPayload(Message message)
+            throws MessagingException {
+        LogUtils.d(TAG, "Fetching message body for " + message.getUid());
+
+        MessageBodyFetchedListener listener = new MessageBodyFetchedListener();
+
+        FetchProfile fetchProfile = new FetchProfile();
+        fetchProfile.add(FetchProfile.Item.BODY);
+
+        mFolder.fetch(new Message[] {message}, fetchProfile, listener);
+        return listener.getVoicemailPayload();
+    }
+
     /**
      * Listener for the message structure being fetched.
      */
@@ -190,9 +235,6 @@
             }
         }
 
-        @Override
-        public void loadAttachmentProgress(int progress) {}
-
         /**
          * Convert an IMAP message to a voicemail object.
          *
@@ -205,14 +247,11 @@
                 LogUtils.w(TAG, "Ignored non multi-part message");
                 return null;
             }
+
             Multipart multipart = (Multipart) message.getBody();
-
-            LogUtils.d(TAG, "Num body parts: " + multipart.getCount());
-
             for (int i = 0; i < multipart.getCount(); ++i) {
                 BodyPart bodyPart = multipart.getBodyPart(i);
                 String bodyPartMimeType = bodyPart.getMimeType().toLowerCase();
-
                 LogUtils.d(TAG, "bodyPart mime type: " + bodyPartMimeType);
 
                 if (bodyPartMimeType.startsWith("audio/")) {
@@ -256,6 +295,60 @@
         }
     }
 
+    /**
+     * Listener for the message body being fetched.
+     */
+    private final class MessageBodyFetchedListener implements ImapFolder.MessageRetrievalListener {
+        private VoicemailPayload mVoicemailPayload;
+
+        /** Returns the fetch voicemail payload. */
+        public VoicemailPayload getVoicemailPayload() {
+            return mVoicemailPayload;
+        }
+
+        @Override
+        public void messageRetrieved(Message message) {
+            LogUtils.d(TAG, "Fetched message body for " + message.getUid());
+            LogUtils.d(TAG, "Message retrieved: " + message);
+            try {
+                mVoicemailPayload = getVoicemailPayloadFromMessage(message);
+            } catch (MessagingException e) {
+                LogUtils.e(TAG, "Messaging Exception:", e);
+            } catch (IOException e) {
+                LogUtils.e(TAG, "IO Exception:", e);
+            }
+        }
+
+        private VoicemailPayload getVoicemailPayloadFromMessage(Message message)
+                throws MessagingException, IOException {
+            Multipart multipart = (Multipart) message.getBody();
+            for (int i = 0; i < multipart.getCount(); ++i) {
+                BodyPart bodyPart = multipart.getBodyPart(i);
+                String bodyPartMimeType = bodyPart.getMimeType().toLowerCase();
+                LogUtils.d(TAG, "bodyPart mime type: " + bodyPartMimeType);
+
+                if (bodyPartMimeType.startsWith("audio/")) {
+                    byte[] bytes = getAudioDataFromBody(bodyPart.getBody());
+                    LogUtils.d(TAG, String.format("Fetched %s bytes of data", bytes.length));
+                    return new VoicemailPayload(bodyPartMimeType, bytes);
+                }
+            }
+            LogUtils.e(TAG, "No audio attachment found on this voicemail");
+            return null;
+        }
+
+        private byte[] getAudioDataFromBody(Body body) throws IOException, MessagingException {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            BufferedOutputStream bufferedOut = new BufferedOutputStream(out);
+            try {
+                body.writeTo(bufferedOut);
+            } finally {
+                IoUtils.closeQuietly(bufferedOut);
+            }
+            return Base64.decode(out.toByteArray(), Base64.DEFAULT);
+        }
+    }
+
     private ImapFolder openImapFolder(String modeReadWrite) {
         try {
             ImapFolder folder = new ImapFolder(mImapStore, ImapConstants.INBOX);
diff --git a/src/com/android/phone/vvm/omtp/imap/VoicemailPayload.java b/src/com/android/phone/vvm/omtp/imap/VoicemailPayload.java
new file mode 100644
index 0000000..0ffa018
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/imap/VoicemailPayload.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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.phone.vvm.omtp.imap;
+
+/**
+ * The payload for a voicemail, usually audio data.
+ */
+public class VoicemailPayload {
+    private final String mMimeType;
+    private final byte[] mBytes;
+
+    public VoicemailPayload(String mimeType, byte[] bytes) {
+        mMimeType = mimeType;
+        mBytes = bytes;
+    }
+
+    public byte[] getBytes() {
+        return mBytes;
+    }
+
+    public String getMimeType() {
+        return mMimeType;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sync/DirtyVoicemailQuery.java b/src/com/android/phone/vvm/omtp/sync/DirtyVoicemailQuery.java
index 965b36b..8c70372 100644
--- a/src/com/android/phone/vvm/omtp/sync/DirtyVoicemailQuery.java
+++ b/src/com/android/phone/vvm/omtp/sync/DirtyVoicemailQuery.java
@@ -48,7 +48,7 @@
      */
     public static Cursor getDirtyVoicemails(Context context) {
         ContentResolver contentResolver = context.getContentResolver();
-        Uri statusUri = VoicemailContract.Voicemails.buildSourceUri(context.getPackageName());
-        return contentResolver.query(statusUri, PROJECTION, SELECTION, null, null);
+        Uri sourceUri = VoicemailContract.Voicemails.buildSourceUri(context.getPackageName());
+        return contentResolver.query(sourceUri, PROJECTION, SELECTION, null, null);
     }
 }
diff --git a/src/com/android/phone/vvm/omtp/sync/FetchVoicemailReceiver.java b/src/com/android/phone/vvm/omtp/sync/FetchVoicemailReceiver.java
new file mode 100644
index 0000000..66fed16
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sync/FetchVoicemailReceiver.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 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.phone.vvm.omtp.sync;
+
+import android.accounts.Account;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Voicemails;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.phone.vvm.omtp.OmtpVvmSyncAccountManager;
+import com.android.phone.vvm.omtp.imap.ImapHelper;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class FetchVoicemailReceiver extends BroadcastReceiver {
+    private static final String TAG = "FetchVoicemailReceiver";
+
+    final static String[] PROJECTION = new String[] {
+        Voicemails.SOURCE_DATA,      // 0
+        Voicemails.PHONE_ACCOUNT_ID, // 1
+    };
+
+    public static final int SOURCE_DATA = 0;
+    public static final int PHONE_ACCOUNT_ID = 1;
+
+    private ContentResolver mContentResolver;
+    private Uri mUri;
+
+    @Override
+    public void onReceive(final Context context, Intent intent) {
+        if (VoicemailContract.ACTION_FETCH_VOICEMAIL.equals(intent.getAction())) {
+            mContentResolver = context.getContentResolver();
+            mUri = intent.getData();
+
+            if (mUri == null) {
+                Log.w(TAG, VoicemailContract.ACTION_FETCH_VOICEMAIL + " intent sent with no data");
+                return;
+            }
+
+            if (!context.getPackageName().equals(
+                    mUri.getQueryParameter(VoicemailContract.PARAM_KEY_SOURCE_PACKAGE))) {
+                // Ignore if the fetch request is for a voicemail not from this package.
+                return;
+            }
+
+            Cursor cursor = mContentResolver.query(mUri, PROJECTION, null, null, null);
+            if (cursor == null) {
+                return;
+            }
+            try {
+                if (cursor.moveToFirst()) {
+                    final String uid = cursor.getString(SOURCE_DATA);
+                    String accountId = cursor.getString(PHONE_ACCOUNT_ID);
+                    if (TextUtils.isEmpty(accountId)) {
+                        TelephonyManager telephonyManager = (TelephonyManager)
+                                context.getSystemService(Context.TELEPHONY_SERVICE);
+                        accountId = telephonyManager.getSimSerialNumber();
+
+                        if (TextUtils.isEmpty(accountId)) {
+                            Log.e(TAG, "Account null and no default sim found.");
+                            return;
+                        }
+                    }
+                    final Account account = new Account(accountId,
+                            OmtpVvmSyncAccountManager.ACCOUNT_TYPE);
+                    Executor executor = Executors.newCachedThreadPool();
+                    executor.execute(new Runnable() {
+                        @Override
+                        public void run() {
+                            new ImapHelper(context, account).fetchVoicemailPayload(
+                                    new VoicemailFetchedCallback(context, mUri), uid);
+                        }
+                    });
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/sync/VoicemailFetchedCallback.java b/src/com/android/phone/vvm/omtp/sync/VoicemailFetchedCallback.java
new file mode 100644
index 0000000..006e83f
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sync/VoicemailFetchedCallback.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 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.phone.vvm.omtp.sync;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.VoicemailContract.Voicemails;
+import android.util.Log;
+
+import com.android.phone.vvm.omtp.imap.VoicemailPayload;
+
+import libcore.io.IoUtils;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Callback for when a voicemail payload is fetched. It copies the returned stream to the data
+ * file corresponding to the voicemail.
+ */
+public class VoicemailFetchedCallback {
+    private static final String TAG = "VoicemailFetchedCallback";
+
+    private ContentResolver mContentResolver;
+    private Uri mUri;
+
+    VoicemailFetchedCallback(Context context, Uri uri) {
+        mContentResolver = context.getContentResolver();
+        mUri = uri;
+    }
+
+    /**
+     * Saves the voicemail payload data into the voicemail provider then sets the "has_content" bit
+     * of the voicemail to "1".
+     *
+     * @param voicemailPayload The object containing the content data for the voicemail
+     */
+    public void setVoicemailContent(VoicemailPayload voicemailPayload) {
+        Log.d(TAG, String.format("Writing new voicemail content: %s", mUri));
+        OutputStream outputStream = null;
+
+        try {
+            outputStream = mContentResolver.openOutputStream(mUri);
+            byte[] inputBytes = voicemailPayload.getBytes();
+            if (inputBytes != null) {
+                outputStream.write(inputBytes);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Error writing to file: ", e);
+        } finally {
+            IoUtils.closeQuietly(outputStream);
+        }
+
+        // Update mime_type & has_content after we are done with file update.
+        ContentValues values = new ContentValues();
+        values.put(Voicemails.MIME_TYPE, voicemailPayload.getMimeType());
+        values.put(Voicemails.HAS_CONTENT, true);
+        int updatedCount = mContentResolver.update(mUri, values, null, null);
+        if (updatedCount != 1) {
+            Log.e(TAG, "Updating voicemail should have updated 1 row, was: " + updatedCount);
+        }
+    }
+}