summaryrefslogtreecommitdiff
path: root/java/com/android/voicemail/impl/imap
diff options
context:
space:
mode:
authorEric Erfanian <erfanian@google.com>2017-03-15 14:41:07 -0700
committerEric Erfanian <erfanian@google.com>2017-03-15 16:24:23 -0700
commitd5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9 (patch)
treeb54abbb51fb7d66e7755a1fbb5db023ff601090b /java/com/android/voicemail/impl/imap
parent30436e7e6d3f2c8755a91b2b6222b74d465a9e87 (diff)
Update Dialer source from latest green build.
* Refactor voicemail component * Add new enriched calling components Test: treehugger, manual aosp testing Change-Id: I521a0f86327d4b42e14d93927c7d613044ed5942
Diffstat (limited to 'java/com/android/voicemail/impl/imap')
-rw-r--r--java/com/android/voicemail/impl/imap/ImapHelper.java693
-rw-r--r--java/com/android/voicemail/impl/imap/VoicemailPayload.java36
2 files changed, 729 insertions, 0 deletions
diff --git a/java/com/android/voicemail/impl/imap/ImapHelper.java b/java/com/android/voicemail/impl/imap/ImapHelper.java
new file mode 100644
index 000000000..6aa415811
--- /dev/null
+++ b/java/com/android/voicemail/impl/imap/ImapHelper.java
@@ -0,0 +1,693 @@
+/*
+ * 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.voicemail.impl.imap;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.provider.VoicemailContract;
+import android.telecom.PhoneAccountHandle;
+import android.util.Base64;
+import com.android.voicemail.impl.OmtpConstants;
+import com.android.voicemail.impl.OmtpConstants.ChangePinResult;
+import com.android.voicemail.impl.OmtpEvents;
+import com.android.voicemail.impl.OmtpVvmCarrierConfigHelper;
+import com.android.voicemail.impl.VisualVoicemailPreferences;
+import com.android.voicemail.impl.Voicemail;
+import com.android.voicemail.impl.VoicemailStatus;
+import com.android.voicemail.impl.VoicemailStatus.Editor;
+import com.android.voicemail.impl.VvmLog;
+import com.android.voicemail.impl.fetch.VoicemailFetchedCallback;
+import com.android.voicemail.impl.mail.Address;
+import com.android.voicemail.impl.mail.Body;
+import com.android.voicemail.impl.mail.BodyPart;
+import com.android.voicemail.impl.mail.FetchProfile;
+import com.android.voicemail.impl.mail.Flag;
+import com.android.voicemail.impl.mail.Message;
+import com.android.voicemail.impl.mail.MessagingException;
+import com.android.voicemail.impl.mail.Multipart;
+import com.android.voicemail.impl.mail.TempDirectory;
+import com.android.voicemail.impl.mail.internet.MimeMessage;
+import com.android.voicemail.impl.mail.store.ImapConnection;
+import com.android.voicemail.impl.mail.store.ImapFolder;
+import com.android.voicemail.impl.mail.store.ImapStore;
+import com.android.voicemail.impl.mail.store.imap.ImapConstants;
+import com.android.voicemail.impl.mail.store.imap.ImapResponse;
+import com.android.voicemail.impl.mail.utils.LogUtils;
+import com.android.voicemail.impl.sync.OmtpVvmSyncService.TranscriptionFetchedCallback;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import org.apache.commons.io.IOUtils;
+
+/** A helper interface to abstract commands sent across IMAP interface for a given account. */
+public class ImapHelper implements Closeable {
+
+ private static final String TAG = "ImapHelper";
+
+ private ImapFolder mFolder;
+ private ImapStore mImapStore;
+
+ private final Context mContext;
+ private final PhoneAccountHandle mPhoneAccount;
+ private final Network mNetwork;
+ private final Editor mStatus;
+
+ VisualVoicemailPreferences mPrefs;
+ private static final String PREF_KEY_QUOTA_OCCUPIED = "quota_occupied_";
+ private static final String PREF_KEY_QUOTA_TOTAL = "quota_total_";
+
+ private int mQuotaOccupied;
+ private int mQuotaTotal;
+
+ private final OmtpVvmCarrierConfigHelper mConfig;
+
+ /** InitializingException */
+ public static class InitializingException extends Exception {
+
+ public InitializingException(String message) {
+ super(message);
+ }
+ }
+
+ public ImapHelper(
+ Context context,
+ PhoneAccountHandle phoneAccount,
+ Network network,
+ Editor status)
+ throws InitializingException {
+ this(
+ context,
+ new OmtpVvmCarrierConfigHelper(context, phoneAccount),
+ phoneAccount,
+ network,
+ status);
+ }
+
+ public ImapHelper(
+ Context context,
+ OmtpVvmCarrierConfigHelper config,
+ PhoneAccountHandle phoneAccount,
+ Network network,
+ Editor status)
+ throws InitializingException {
+ mContext = context;
+ mPhoneAccount = phoneAccount;
+ mNetwork = network;
+ mStatus = status;
+ mConfig = config;
+ mPrefs = new VisualVoicemailPreferences(context, phoneAccount);
+
+ try {
+ TempDirectory.setTempDirectory(context);
+
+ String username = mPrefs.getString(OmtpConstants.IMAP_USER_NAME, null);
+ String password = mPrefs.getString(OmtpConstants.IMAP_PASSWORD, null);
+ String serverName = mPrefs.getString(OmtpConstants.SERVER_ADDRESS, null);
+ int port = Integer.parseInt(mPrefs.getString(OmtpConstants.IMAP_PORT, null));
+ int auth = ImapStore.FLAG_NONE;
+
+ int sslPort = mConfig.getSslPort();
+ if (sslPort != 0) {
+ port = sslPort;
+ auth = ImapStore.FLAG_SSL;
+ }
+
+ mImapStore =
+ new ImapStore(context, this, username, password, port, serverName, auth, network);
+ } catch (NumberFormatException e) {
+ handleEvent(OmtpEvents.DATA_INVALID_PORT);
+ LogUtils.w(TAG, "Could not parse port number");
+ throw new InitializingException("cannot initialize ImapHelper:" + e.toString());
+ }
+
+ mQuotaOccupied =
+ mPrefs.getInt(PREF_KEY_QUOTA_OCCUPIED, VoicemailContract.Status.QUOTA_UNAVAILABLE);
+ mQuotaTotal = mPrefs.getInt(PREF_KEY_QUOTA_TOTAL, VoicemailContract.Status.QUOTA_UNAVAILABLE);
+ }
+
+ @Override
+ public void close() {
+ mImapStore.closeConnection();
+ }
+
+ public boolean isRoaming() {
+ ConnectivityManager connectivityManager =
+ (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo info = connectivityManager.getNetworkInfo(mNetwork);
+ if (info == null) {
+ return false;
+ }
+ return info.isRoaming();
+ }
+
+ public OmtpVvmCarrierConfigHelper getConfig() {
+ return mConfig;
+ }
+
+ public ImapConnection connect() {
+ return mImapStore.getConnection();
+ }
+
+ /** The caller thread will block until the method returns. */
+ public boolean markMessagesAsRead(List<Voicemail> voicemails) {
+ return setFlags(voicemails, Flag.SEEN);
+ }
+
+ /** The caller thread will block until the method returns. */
+ public boolean markMessagesAsDeleted(List<Voicemail> voicemails) {
+ return setFlags(voicemails, Flag.DELETED);
+ }
+
+ public void handleEvent(OmtpEvents event) {
+ mConfig.handleEvent(mStatus, event);
+ }
+
+ /**
+ * Set flags on the server for a given set of voicemails.
+ *
+ * @param voicemails The voicemails to set flags for.
+ * @param flags The flags to set on the voicemails.
+ * @return {@code true} if the operation completes successfully, {@code false} otherwise.
+ */
+ private boolean setFlags(List<Voicemail> voicemails, String... flags) {
+ if (voicemails.size() == 0) {
+ return false;
+ }
+ try {
+ mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE);
+ if (mFolder != null) {
+ mFolder.setFlags(convertToImapMessages(voicemails), flags, true);
+ return true;
+ }
+ return false;
+ } catch (MessagingException e) {
+ LogUtils.e(TAG, e, "Messaging exception");
+ return false;
+ } finally {
+ closeImapFolder();
+ }
+ }
+
+ /**
+ * Fetch a list of voicemails from the server.
+ *
+ * @return A list of voicemail objects containing data about voicemails stored on the server.
+ */
+ public List<Voicemail> fetchAllVoicemails() {
+ List<Voicemail> result = new ArrayList<Voicemail>();
+ Message[] messages;
+ try {
+ mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE);
+ if (mFolder == null) {
+ // This means we were unable to successfully open the folder.
+ return null;
+ }
+
+ // This method retrieves lightweight messages containing only the uid of the message.
+ messages = mFolder.getMessages(null);
+
+ for (Message message : messages) {
+ // Get the voicemail details (message structure).
+ MessageStructureWrapper messageStructureWrapper = fetchMessageStructure(message);
+ if (messageStructureWrapper != null) {
+ result.add(getVoicemailFromMessageStructure(messageStructureWrapper));
+ }
+ }
+ return result;
+ } catch (MessagingException e) {
+ LogUtils.e(TAG, e, "Messaging Exception");
+ return null;
+ } finally {
+ closeImapFolder();
+ }
+ }
+
+ /**
+ * Extract voicemail details from the message structure. Also fetch transcription if a
+ * transcription exists.
+ */
+ private Voicemail getVoicemailFromMessageStructure(
+ MessageStructureWrapper messageStructureWrapper) throws MessagingException {
+ Message messageDetails = messageStructureWrapper.messageStructure;
+
+ TranscriptionFetchedListener listener = new TranscriptionFetchedListener();
+ if (messageStructureWrapper.transcriptionBodyPart != null) {
+ FetchProfile fetchProfile = new FetchProfile();
+ fetchProfile.add(messageStructureWrapper.transcriptionBodyPart);
+
+ mFolder.fetch(new Message[] {messageDetails}, fetchProfile, listener);
+ }
+
+ // Found an audio attachment, this is a valid voicemail.
+ long time = messageDetails.getSentDate().getTime();
+ String number = getNumber(messageDetails.getFrom());
+ boolean isRead = Arrays.asList(messageDetails.getFlags()).contains(Flag.SEEN);
+ return Voicemail.createForInsertion(time, number)
+ .setPhoneAccount(mPhoneAccount)
+ .setSourcePackage(mContext.getPackageName())
+ .setSourceData(messageDetails.getUid())
+ .setIsRead(isRead)
+ .setTranscription(listener.getVoicemailTranscription())
+ .build();
+ }
+
+ /**
+ * The "from" field of a visual voicemail IMAP message is the number of the caller who left the
+ * message. Extract this number from the list of "from" addresses.
+ *
+ * @param fromAddresses A list of addresses that comprise the "from" line.
+ * @return The number of the voicemail sender.
+ */
+ private String getNumber(Address[] fromAddresses) {
+ if (fromAddresses != null && fromAddresses.length > 0) {
+ if (fromAddresses.length != 1) {
+ LogUtils.w(TAG, "More than one from addresses found. Using the first one.");
+ }
+ String sender = fromAddresses[0].getAddress();
+ int atPos = sender.indexOf('@');
+ if (atPos != -1) {
+ // Strip domain part of the address.
+ sender = sender.substring(0, atPos);
+ }
+ return sender;
+ }
+ return null;
+ }
+
+ /**
+ * Fetches the structure of the given message and returns a wrapper containing the message
+ * structure and the transcription structure (if applicable).
+ *
+ * @throws MessagingException if fetching the structure of the message fails
+ */
+ private MessageStructureWrapper fetchMessageStructure(Message message) throws MessagingException {
+ LogUtils.d(TAG, "Fetching message structure for " + message.getUid());
+
+ MessageStructureFetchedListener listener = new MessageStructureFetchedListener();
+
+ FetchProfile fetchProfile = new FetchProfile();
+ fetchProfile.addAll(
+ Arrays.asList(
+ FetchProfile.Item.FLAGS, FetchProfile.Item.ENVELOPE, FetchProfile.Item.STRUCTURE));
+
+ // The IMAP folder fetch method will call "messageRetrieved" on the listener when the
+ // message is successfully retrieved.
+ mFolder.fetch(new Message[] {message}, fetchProfile, listener);
+ return listener.getMessageStructure();
+ }
+
+ public boolean fetchVoicemailPayload(VoicemailFetchedCallback callback, final String uid) {
+ try {
+ mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE);
+ if (mFolder == null) {
+ // This means we were unable to successfully open the folder.
+ return false;
+ }
+ Message message = mFolder.getMessage(uid);
+ if (message == null) {
+ return false;
+ }
+ VoicemailPayload voicemailPayload = fetchVoicemailPayload(message);
+ callback.setVoicemailContent(voicemailPayload);
+ return true;
+ } catch (MessagingException e) {
+ } finally {
+ closeImapFolder();
+ }
+ return false;
+ }
+
+ /**
+ * 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();
+ }
+
+ public boolean fetchTranscription(TranscriptionFetchedCallback callback, String uid) {
+ try {
+ mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE);
+ if (mFolder == null) {
+ // This means we were unable to successfully open the folder.
+ return false;
+ }
+
+ Message message = mFolder.getMessage(uid);
+ if (message == null) {
+ return false;
+ }
+
+ MessageStructureWrapper messageStructureWrapper = fetchMessageStructure(message);
+ if (messageStructureWrapper != null) {
+ TranscriptionFetchedListener listener = new TranscriptionFetchedListener();
+ if (messageStructureWrapper.transcriptionBodyPart != null) {
+ FetchProfile fetchProfile = new FetchProfile();
+ fetchProfile.add(messageStructureWrapper.transcriptionBodyPart);
+
+ // This method is called synchronously so the transcription will be populated
+ // in the listener once the next method is called.
+ mFolder.fetch(new Message[] {message}, fetchProfile, listener);
+ callback.setVoicemailTranscription(listener.getVoicemailTranscription());
+ }
+ }
+ return true;
+ } catch (MessagingException e) {
+ LogUtils.e(TAG, e, "Messaging Exception");
+ return false;
+ } finally {
+ closeImapFolder();
+ }
+ }
+
+ @ChangePinResult
+ public int changePin(String oldPin, String newPin) throws MessagingException {
+ ImapConnection connection = mImapStore.getConnection();
+ try {
+ String command =
+ getConfig().getProtocol().getCommand(OmtpConstants.IMAP_CHANGE_TUI_PWD_FORMAT);
+ connection.sendCommand(String.format(Locale.US, command, newPin, oldPin), true);
+ return getChangePinResultFromImapResponse(connection.readResponse());
+ } catch (IOException ioe) {
+ VvmLog.e(TAG, "changePin: ", ioe);
+ return OmtpConstants.CHANGE_PIN_SYSTEM_ERROR;
+ } finally {
+ connection.destroyResponses();
+ }
+ }
+
+ public void changeVoicemailTuiLanguage(String languageCode) throws MessagingException {
+ ImapConnection connection = mImapStore.getConnection();
+ try {
+ String command =
+ getConfig().getProtocol().getCommand(OmtpConstants.IMAP_CHANGE_VM_LANG_FORMAT);
+ connection.sendCommand(String.format(Locale.US, command, languageCode), true);
+ } catch (IOException ioe) {
+ LogUtils.e(TAG, ioe.toString());
+ } finally {
+ connection.destroyResponses();
+ }
+ }
+
+ public void closeNewUserTutorial() throws MessagingException {
+ ImapConnection connection = mImapStore.getConnection();
+ try {
+ String command = getConfig().getProtocol().getCommand(OmtpConstants.IMAP_CLOSE_NUT);
+ connection.executeSimpleCommand(command, false);
+ } catch (IOException ioe) {
+ throw new MessagingException(MessagingException.SERVER_ERROR, ioe.toString());
+ } finally {
+ connection.destroyResponses();
+ }
+ }
+
+ @ChangePinResult
+ private static int getChangePinResultFromImapResponse(ImapResponse response)
+ throws MessagingException {
+ if (!response.isTagged()) {
+ throw new MessagingException(MessagingException.SERVER_ERROR, "tagged response expected");
+ }
+ if (!response.isOk()) {
+ String message = response.getStringOrEmpty(1).getString();
+ LogUtils.d(TAG, "change PIN failed: " + message);
+ if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_SHORT.equals(message)) {
+ return OmtpConstants.CHANGE_PIN_TOO_SHORT;
+ }
+ if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_LONG.equals(message)) {
+ return OmtpConstants.CHANGE_PIN_TOO_LONG;
+ }
+ if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_WEAK.equals(message)) {
+ return OmtpConstants.CHANGE_PIN_TOO_WEAK;
+ }
+ if (OmtpConstants.RESPONSE_CHANGE_PIN_MISMATCH.equals(message)) {
+ return OmtpConstants.CHANGE_PIN_MISMATCH;
+ }
+ if (OmtpConstants.RESPONSE_CHANGE_PIN_INVALID_CHARACTER.equals(message)) {
+ return OmtpConstants.CHANGE_PIN_INVALID_CHARACTER;
+ }
+ return OmtpConstants.CHANGE_PIN_SYSTEM_ERROR;
+ }
+ LogUtils.d(TAG, "change PIN succeeded");
+ return OmtpConstants.CHANGE_PIN_SUCCESS;
+ }
+
+ public void updateQuota() {
+ try {
+ mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE);
+ if (mFolder == null) {
+ // This means we were unable to successfully open the folder.
+ return;
+ }
+ updateQuota(mFolder);
+ } catch (MessagingException e) {
+ LogUtils.e(TAG, e, "Messaging Exception");
+ } finally {
+ closeImapFolder();
+ }
+ }
+
+ public int getOccuupiedQuota() {
+ return mQuotaOccupied;
+ }
+
+ public int getTotalQuota() {
+ return mQuotaTotal;
+ }
+
+ private void updateQuota(ImapFolder folder) throws MessagingException {
+ setQuota(folder.getQuota());
+ }
+
+ private void setQuota(ImapFolder.Quota quota) {
+ if (quota == null) {
+ return;
+ }
+ if (quota.occupied == mQuotaOccupied && quota.total == mQuotaTotal) {
+ VvmLog.v(TAG, "Quota hasn't changed");
+ return;
+ }
+ mQuotaOccupied = quota.occupied;
+ mQuotaTotal = quota.total;
+ VoicemailStatus.edit(mContext, mPhoneAccount).setQuota(mQuotaOccupied, mQuotaTotal).apply();
+ mPrefs
+ .edit()
+ .putInt(PREF_KEY_QUOTA_OCCUPIED, mQuotaOccupied)
+ .putInt(PREF_KEY_QUOTA_TOTAL, mQuotaTotal)
+ .apply();
+ VvmLog.v(TAG, "Quota changed to " + mQuotaOccupied + "/" + mQuotaTotal);
+ }
+
+ /**
+ * A wrapper to hold a message with its header details and the structure for transcriptions (so
+ * they can be fetched in the future).
+ */
+ public static class MessageStructureWrapper {
+
+ public Message messageStructure;
+ public BodyPart transcriptionBodyPart;
+
+ public MessageStructureWrapper() {}
+ }
+
+ /** Listener for the message structure being fetched. */
+ private final class MessageStructureFetchedListener
+ implements ImapFolder.MessageRetrievalListener {
+
+ private MessageStructureWrapper mMessageStructure;
+
+ public MessageStructureFetchedListener() {}
+
+ public MessageStructureWrapper getMessageStructure() {
+ return mMessageStructure;
+ }
+
+ @Override
+ public void messageRetrieved(Message message) {
+ LogUtils.d(TAG, "Fetched message structure for " + message.getUid());
+ LogUtils.d(TAG, "Message retrieved: " + message);
+ try {
+ mMessageStructure = getMessageOrNull(message);
+ if (mMessageStructure == null) {
+ LogUtils.d(TAG, "This voicemail does not have an attachment...");
+ return;
+ }
+ } catch (MessagingException e) {
+ LogUtils.e(TAG, e, "Messaging Exception");
+ closeImapFolder();
+ }
+ }
+
+ /**
+ * Check if this IMAP message is a valid voicemail and whether it contains a transcription.
+ *
+ * @param message The IMAP message.
+ * @return The MessageStructureWrapper object corresponding to an IMAP message and
+ * transcription.
+ */
+ private MessageStructureWrapper getMessageOrNull(Message message) throws MessagingException {
+ if (!message.getMimeType().startsWith("multipart/")) {
+ LogUtils.w(TAG, "Ignored non multi-part message");
+ return null;
+ }
+
+ MessageStructureWrapper messageStructureWrapper = new MessageStructureWrapper();
+
+ 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/")) {
+ messageStructureWrapper.messageStructure = message;
+ } else if (bodyPartMimeType.startsWith("text/")) {
+ messageStructureWrapper.transcriptionBodyPart = bodyPart;
+ } else {
+ VvmLog.v(TAG, "Unknown bodyPart MIME: " + bodyPartMimeType);
+ }
+ }
+
+ if (messageStructureWrapper.messageStructure != null) {
+ return messageStructureWrapper;
+ }
+
+ // No attachment found, this is not a voicemail.
+ return null;
+ }
+ }
+
+ /** 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();
+ List<String> mimeTypes = new ArrayList<>();
+ for (int i = 0; i < multipart.getCount(); ++i) {
+ BodyPart bodyPart = multipart.getBodyPart(i);
+ String bodyPartMimeType = bodyPart.getMimeType().toLowerCase();
+ mimeTypes.add(bodyPartMimeType);
+ if (bodyPartMimeType.startsWith("audio/")) {
+ byte[] bytes = getDataFromBody(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, mimeTypes:" + mimeTypes);
+ return null;
+ }
+ }
+
+ /** Listener for the transcription being fetched. */
+ private final class TranscriptionFetchedListener implements ImapFolder.MessageRetrievalListener {
+
+ private String mVoicemailTranscription;
+
+ /** Returns the fetched voicemail transcription. */
+ public String getVoicemailTranscription() {
+ return mVoicemailTranscription;
+ }
+
+ @Override
+ public void messageRetrieved(Message message) {
+ LogUtils.d(TAG, "Fetched transcription for " + message.getUid());
+ try {
+ mVoicemailTranscription = new String(getDataFromBody(message.getBody()));
+ } catch (MessagingException e) {
+ LogUtils.e(TAG, "Messaging Exception:", e);
+ } catch (IOException e) {
+ LogUtils.e(TAG, "IO Exception:", e);
+ }
+ }
+ }
+
+ private ImapFolder openImapFolder(String modeReadWrite) {
+ try {
+ if (mImapStore == null) {
+ return null;
+ }
+ ImapFolder folder = new ImapFolder(mImapStore, ImapConstants.INBOX);
+ folder.open(modeReadWrite);
+ return folder;
+ } catch (MessagingException e) {
+ LogUtils.e(TAG, e, "Messaging Exception");
+ }
+ return null;
+ }
+
+ private Message[] convertToImapMessages(List<Voicemail> voicemails) {
+ Message[] messages = new Message[voicemails.size()];
+ for (int i = 0; i < voicemails.size(); ++i) {
+ messages[i] = new MimeMessage();
+ messages[i].setUid(voicemails.get(i).getSourceData());
+ }
+ return messages;
+ }
+
+ private void closeImapFolder() {
+ if (mFolder != null) {
+ mFolder.close(true);
+ }
+ }
+
+ private byte[] getDataFromBody(Body body) throws IOException, MessagingException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ BufferedOutputStream bufferedOut = new BufferedOutputStream(out);
+ try {
+ body.writeTo(bufferedOut);
+ return Base64.decode(out.toByteArray(), Base64.DEFAULT);
+ } finally {
+ IOUtils.closeQuietly(bufferedOut);
+ IOUtils.closeQuietly(out);
+ }
+ }
+}
diff --git a/java/com/android/voicemail/impl/imap/VoicemailPayload.java b/java/com/android/voicemail/impl/imap/VoicemailPayload.java
new file mode 100644
index 000000000..69befb42f
--- /dev/null
+++ b/java/com/android/voicemail/impl/imap/VoicemailPayload.java
@@ -0,0 +1,36 @@
+/*
+ * 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.voicemail.impl.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;
+ }
+}