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);
+ }
+ }
+}