696 lines
24 KiB
Java
696 lines
24 KiB
Java
/*
|
|
* 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.support.annotation.Nullable;
|
|
import android.telecom.PhoneAccountHandle;
|
|
import android.util.Base64;
|
|
import com.android.voicemail.PinChanger;
|
|
import com.android.voicemail.PinChanger.ChangePinResult;
|
|
import com.android.voicemail.impl.OmtpConstants;
|
|
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.ImapFolder.Quota;
|
|
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 folder;
|
|
private ImapStore imapStore;
|
|
|
|
private final Context context;
|
|
private final PhoneAccountHandle phoneAccount;
|
|
private final Network network;
|
|
private final Editor status;
|
|
|
|
VisualVoicemailPreferences prefs;
|
|
|
|
private final OmtpVvmCarrierConfigHelper config;
|
|
|
|
/** 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 {
|
|
this.context = context;
|
|
this.phoneAccount = phoneAccount;
|
|
this.network = network;
|
|
this.status = status;
|
|
this.config = config;
|
|
prefs = new VisualVoicemailPreferences(context, phoneAccount);
|
|
|
|
try {
|
|
TempDirectory.setTempDirectory(context);
|
|
|
|
String username = prefs.getString(OmtpConstants.IMAP_USER_NAME, null);
|
|
String password = prefs.getString(OmtpConstants.IMAP_PASSWORD, null);
|
|
String serverName = prefs.getString(OmtpConstants.SERVER_ADDRESS, null);
|
|
int port = Integer.parseInt(prefs.getString(OmtpConstants.IMAP_PORT, null));
|
|
int auth = ImapStore.FLAG_NONE;
|
|
|
|
int sslPort = this.config.getSslPort();
|
|
if (sslPort != 0) {
|
|
port = sslPort;
|
|
auth = ImapStore.FLAG_SSL;
|
|
}
|
|
|
|
imapStore = 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());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
imapStore.closeConnection();
|
|
}
|
|
|
|
public boolean isRoaming() {
|
|
ConnectivityManager connectivityManager =
|
|
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
NetworkInfo info = connectivityManager.getNetworkInfo(network);
|
|
if (info == null) {
|
|
return false;
|
|
}
|
|
return info.isRoaming();
|
|
}
|
|
|
|
public OmtpVvmCarrierConfigHelper getConfig() {
|
|
return config;
|
|
}
|
|
|
|
public ImapConnection connect() {
|
|
return imapStore.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) {
|
|
config.handleEvent(status, 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 {
|
|
folder = openImapFolder(ImapFolder.MODE_READ_WRITE);
|
|
if (folder != null) {
|
|
folder.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 {
|
|
folder = openImapFolder(ImapFolder.MODE_READ_WRITE);
|
|
if (folder == 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 = folder.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);
|
|
|
|
folder.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);
|
|
Long duration = messageDetails.getDuration();
|
|
Voicemail.Builder builder =
|
|
Voicemail.createForInsertion(time, number)
|
|
.setPhoneAccount(phoneAccount)
|
|
.setSourcePackage(context.getPackageName())
|
|
.setSourceData(messageDetails.getUid())
|
|
.setIsRead(isRead)
|
|
.setTranscription(listener.getVoicemailTranscription());
|
|
if (duration != null) {
|
|
builder.setDuration(duration);
|
|
}
|
|
return builder.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.
|
|
folder.fetch(new Message[] {message}, fetchProfile, listener);
|
|
return listener.getMessageStructure();
|
|
}
|
|
|
|
public boolean fetchVoicemailPayload(VoicemailFetchedCallback callback, final String uid) {
|
|
try {
|
|
folder = openImapFolder(ImapFolder.MODE_READ_WRITE);
|
|
if (folder == null) {
|
|
// This means we were unable to successfully open the folder.
|
|
return false;
|
|
}
|
|
Message message = folder.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);
|
|
|
|
folder.fetch(new Message[] {message}, fetchProfile, listener);
|
|
return listener.getVoicemailPayload();
|
|
}
|
|
|
|
public boolean fetchTranscription(TranscriptionFetchedCallback callback, String uid) {
|
|
try {
|
|
folder = openImapFolder(ImapFolder.MODE_READ_WRITE);
|
|
if (folder == null) {
|
|
// This means we were unable to successfully open the folder.
|
|
return false;
|
|
}
|
|
|
|
Message message = folder.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.
|
|
folder.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 = imapStore.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 PinChanger.CHANGE_PIN_SYSTEM_ERROR;
|
|
} finally {
|
|
connection.destroyResponses();
|
|
}
|
|
}
|
|
|
|
public void changeVoicemailTuiLanguage(String languageCode) throws MessagingException {
|
|
ImapConnection connection = imapStore.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 = imapStore.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 PinChanger.CHANGE_PIN_TOO_SHORT;
|
|
}
|
|
if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_LONG.equals(message)) {
|
|
return PinChanger.CHANGE_PIN_TOO_LONG;
|
|
}
|
|
if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_WEAK.equals(message)) {
|
|
return PinChanger.CHANGE_PIN_TOO_WEAK;
|
|
}
|
|
if (OmtpConstants.RESPONSE_CHANGE_PIN_MISMATCH.equals(message)) {
|
|
return PinChanger.CHANGE_PIN_MISMATCH;
|
|
}
|
|
if (OmtpConstants.RESPONSE_CHANGE_PIN_INVALID_CHARACTER.equals(message)) {
|
|
return PinChanger.CHANGE_PIN_INVALID_CHARACTER;
|
|
}
|
|
return PinChanger.CHANGE_PIN_SYSTEM_ERROR;
|
|
}
|
|
LogUtils.d(TAG, "change PIN succeeded");
|
|
return PinChanger.CHANGE_PIN_SUCCESS;
|
|
}
|
|
|
|
public void updateQuota() {
|
|
try {
|
|
folder = openImapFolder(ImapFolder.MODE_READ_WRITE);
|
|
if (folder == null) {
|
|
// This means we were unable to successfully open the folder.
|
|
return;
|
|
}
|
|
updateQuota(folder);
|
|
} catch (MessagingException e) {
|
|
LogUtils.e(TAG, e, "Messaging Exception");
|
|
} finally {
|
|
closeImapFolder();
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public Quota getQuota() {
|
|
try {
|
|
folder = openImapFolder(ImapFolder.MODE_READ_ONLY);
|
|
if (folder == null) {
|
|
// This means we were unable to successfully open the folder.
|
|
LogUtils.e(TAG, "Unable to open folder");
|
|
return null;
|
|
}
|
|
return folder.getQuota();
|
|
} catch (MessagingException e) {
|
|
LogUtils.e(TAG, e, "Messaging Exception");
|
|
return null;
|
|
} finally {
|
|
closeImapFolder();
|
|
}
|
|
}
|
|
|
|
private void updateQuota(ImapFolder folder) throws MessagingException {
|
|
setQuota(folder.getQuota());
|
|
}
|
|
|
|
private void setQuota(ImapFolder.Quota quota) {
|
|
if (quota == null) {
|
|
LogUtils.i(TAG, "quota was null");
|
|
return;
|
|
}
|
|
|
|
LogUtils.i(
|
|
TAG,
|
|
"Updating Voicemail status table with"
|
|
+ " quota occupied: "
|
|
+ quota.occupied
|
|
+ " new quota total:"
|
|
+ quota.total);
|
|
VoicemailStatus.edit(context, phoneAccount).setQuota(quota.occupied, quota.total).apply();
|
|
LogUtils.i(TAG, "Updated quota occupied and total");
|
|
}
|
|
|
|
/**
|
|
* 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 messageStructure;
|
|
|
|
public MessageStructureFetchedListener() {}
|
|
|
|
public MessageStructureWrapper getMessageStructure() {
|
|
return messageStructure;
|
|
}
|
|
|
|
@Override
|
|
public void messageRetrieved(Message message) {
|
|
LogUtils.d(TAG, "Fetched message structure for " + message.getUid());
|
|
LogUtils.d(TAG, "Message retrieved: " + message);
|
|
try {
|
|
messageStructure = getMessageOrNull(message);
|
|
if (messageStructure == 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 (!config.ignoreTranscription() && 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 voicemailPayload;
|
|
|
|
/** Returns the fetch voicemail payload. */
|
|
public VoicemailPayload getVoicemailPayload() {
|
|
return voicemailPayload;
|
|
}
|
|
|
|
@Override
|
|
public void messageRetrieved(Message message) {
|
|
LogUtils.d(TAG, "Fetched message body for " + message.getUid());
|
|
LogUtils.d(TAG, "Message retrieved: " + message);
|
|
try {
|
|
voicemailPayload = 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 voicemailTranscription;
|
|
|
|
/** Returns the fetched voicemail transcription. */
|
|
public String getVoicemailTranscription() {
|
|
return voicemailTranscription;
|
|
}
|
|
|
|
@Override
|
|
public void messageRetrieved(Message message) {
|
|
LogUtils.d(TAG, "Fetched transcription for " + message.getUid());
|
|
try {
|
|
voicemailTranscription = 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 (imapStore == null) {
|
|
return null;
|
|
}
|
|
ImapFolder folder = new ImapFolder(imapStore, 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 (folder != null) {
|
|
folder.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);
|
|
}
|
|
}
|
|
}
|