1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 package com.android.voicemail.impl.imap; 17 18 import android.content.Context; 19 import android.net.ConnectivityManager; 20 import android.net.Network; 21 import android.net.NetworkInfo; 22 import android.support.annotation.Nullable; 23 import android.telecom.PhoneAccountHandle; 24 import android.util.Base64; 25 import com.android.voicemail.impl.OmtpConstants; 26 import com.android.voicemail.impl.OmtpConstants.ChangePinResult; 27 import com.android.voicemail.impl.OmtpEvents; 28 import com.android.voicemail.impl.OmtpVvmCarrierConfigHelper; 29 import com.android.voicemail.impl.VisualVoicemailPreferences; 30 import com.android.voicemail.impl.Voicemail; 31 import com.android.voicemail.impl.VoicemailStatus; 32 import com.android.voicemail.impl.VoicemailStatus.Editor; 33 import com.android.voicemail.impl.VvmLog; 34 import com.android.voicemail.impl.fetch.VoicemailFetchedCallback; 35 import com.android.voicemail.impl.mail.Address; 36 import com.android.voicemail.impl.mail.Body; 37 import com.android.voicemail.impl.mail.BodyPart; 38 import com.android.voicemail.impl.mail.FetchProfile; 39 import com.android.voicemail.impl.mail.Flag; 40 import com.android.voicemail.impl.mail.Message; 41 import com.android.voicemail.impl.mail.MessagingException; 42 import com.android.voicemail.impl.mail.Multipart; 43 import com.android.voicemail.impl.mail.TempDirectory; 44 import com.android.voicemail.impl.mail.internet.MimeMessage; 45 import com.android.voicemail.impl.mail.store.ImapConnection; 46 import com.android.voicemail.impl.mail.store.ImapFolder; 47 import com.android.voicemail.impl.mail.store.ImapFolder.Quota; 48 import com.android.voicemail.impl.mail.store.ImapStore; 49 import com.android.voicemail.impl.mail.store.imap.ImapConstants; 50 import com.android.voicemail.impl.mail.store.imap.ImapResponse; 51 import com.android.voicemail.impl.mail.utils.LogUtils; 52 import com.android.voicemail.impl.sync.OmtpVvmSyncService.TranscriptionFetchedCallback; 53 import java.io.BufferedOutputStream; 54 import java.io.ByteArrayOutputStream; 55 import java.io.Closeable; 56 import java.io.IOException; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.List; 60 import java.util.Locale; 61 import org.apache.commons.io.IOUtils; 62 63 /** A helper interface to abstract commands sent across IMAP interface for a given account. */ 64 public class ImapHelper implements Closeable { 65 66 private static final String TAG = "ImapHelper"; 67 68 private ImapFolder mFolder; 69 private ImapStore mImapStore; 70 71 private final Context mContext; 72 private final PhoneAccountHandle mPhoneAccount; 73 private final Network mNetwork; 74 private final Editor mStatus; 75 76 VisualVoicemailPreferences mPrefs; 77 78 private final OmtpVvmCarrierConfigHelper mConfig; 79 80 /** InitializingException */ 81 public static class InitializingException extends Exception { 82 InitializingException(String message)83 public InitializingException(String message) { 84 super(message); 85 } 86 } 87 ImapHelper( Context context, PhoneAccountHandle phoneAccount, Network network, Editor status)88 public ImapHelper( 89 Context context, PhoneAccountHandle phoneAccount, Network network, Editor status) 90 throws InitializingException { 91 this( 92 context, 93 new OmtpVvmCarrierConfigHelper(context, phoneAccount), 94 phoneAccount, 95 network, 96 status); 97 } 98 ImapHelper( Context context, OmtpVvmCarrierConfigHelper config, PhoneAccountHandle phoneAccount, Network network, Editor status)99 public ImapHelper( 100 Context context, 101 OmtpVvmCarrierConfigHelper config, 102 PhoneAccountHandle phoneAccount, 103 Network network, 104 Editor status) 105 throws InitializingException { 106 mContext = context; 107 mPhoneAccount = phoneAccount; 108 mNetwork = network; 109 mStatus = status; 110 mConfig = config; 111 mPrefs = new VisualVoicemailPreferences(context, phoneAccount); 112 113 try { 114 TempDirectory.setTempDirectory(context); 115 116 String username = mPrefs.getString(OmtpConstants.IMAP_USER_NAME, null); 117 String password = mPrefs.getString(OmtpConstants.IMAP_PASSWORD, null); 118 String serverName = mPrefs.getString(OmtpConstants.SERVER_ADDRESS, null); 119 int port = Integer.parseInt(mPrefs.getString(OmtpConstants.IMAP_PORT, null)); 120 int auth = ImapStore.FLAG_NONE; 121 122 int sslPort = mConfig.getSslPort(); 123 if (sslPort != 0) { 124 port = sslPort; 125 auth = ImapStore.FLAG_SSL; 126 } 127 128 mImapStore = 129 new ImapStore(context, this, username, password, port, serverName, auth, network); 130 } catch (NumberFormatException e) { 131 handleEvent(OmtpEvents.DATA_INVALID_PORT); 132 LogUtils.w(TAG, "Could not parse port number"); 133 throw new InitializingException("cannot initialize ImapHelper:" + e.toString()); 134 } 135 } 136 137 @Override close()138 public void close() { 139 mImapStore.closeConnection(); 140 } 141 isRoaming()142 public boolean isRoaming() { 143 ConnectivityManager connectivityManager = 144 (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 145 NetworkInfo info = connectivityManager.getNetworkInfo(mNetwork); 146 if (info == null) { 147 return false; 148 } 149 return info.isRoaming(); 150 } 151 getConfig()152 public OmtpVvmCarrierConfigHelper getConfig() { 153 return mConfig; 154 } 155 connect()156 public ImapConnection connect() { 157 return mImapStore.getConnection(); 158 } 159 160 /** The caller thread will block until the method returns. */ markMessagesAsRead(List<Voicemail> voicemails)161 public boolean markMessagesAsRead(List<Voicemail> voicemails) { 162 return setFlags(voicemails, Flag.SEEN); 163 } 164 165 /** The caller thread will block until the method returns. */ markMessagesAsDeleted(List<Voicemail> voicemails)166 public boolean markMessagesAsDeleted(List<Voicemail> voicemails) { 167 return setFlags(voicemails, Flag.DELETED); 168 } 169 handleEvent(OmtpEvents event)170 public void handleEvent(OmtpEvents event) { 171 mConfig.handleEvent(mStatus, event); 172 } 173 174 /** 175 * Set flags on the server for a given set of voicemails. 176 * 177 * @param voicemails The voicemails to set flags for. 178 * @param flags The flags to set on the voicemails. 179 * @return {@code true} if the operation completes successfully, {@code false} otherwise. 180 */ setFlags(List<Voicemail> voicemails, String... flags)181 private boolean setFlags(List<Voicemail> voicemails, String... flags) { 182 if (voicemails.size() == 0) { 183 return false; 184 } 185 try { 186 mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE); 187 if (mFolder != null) { 188 mFolder.setFlags(convertToImapMessages(voicemails), flags, true); 189 return true; 190 } 191 return false; 192 } catch (MessagingException e) { 193 LogUtils.e(TAG, e, "Messaging exception"); 194 return false; 195 } finally { 196 closeImapFolder(); 197 } 198 } 199 200 /** 201 * Fetch a list of voicemails from the server. 202 * 203 * @return A list of voicemail objects containing data about voicemails stored on the server. 204 */ fetchAllVoicemails()205 public List<Voicemail> fetchAllVoicemails() { 206 List<Voicemail> result = new ArrayList<Voicemail>(); 207 Message[] messages; 208 try { 209 mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE); 210 if (mFolder == null) { 211 // This means we were unable to successfully open the folder. 212 return null; 213 } 214 215 // This method retrieves lightweight messages containing only the uid of the message. 216 messages = mFolder.getMessages(null); 217 218 for (Message message : messages) { 219 // Get the voicemail details (message structure). 220 MessageStructureWrapper messageStructureWrapper = fetchMessageStructure(message); 221 if (messageStructureWrapper != null) { 222 result.add(getVoicemailFromMessageStructure(messageStructureWrapper)); 223 } 224 } 225 return result; 226 } catch (MessagingException e) { 227 LogUtils.e(TAG, e, "Messaging Exception"); 228 return null; 229 } finally { 230 closeImapFolder(); 231 } 232 } 233 234 /** 235 * Extract voicemail details from the message structure. Also fetch transcription if a 236 * transcription exists. 237 */ getVoicemailFromMessageStructure( MessageStructureWrapper messageStructureWrapper)238 private Voicemail getVoicemailFromMessageStructure( 239 MessageStructureWrapper messageStructureWrapper) throws MessagingException { 240 Message messageDetails = messageStructureWrapper.messageStructure; 241 242 TranscriptionFetchedListener listener = new TranscriptionFetchedListener(); 243 if (messageStructureWrapper.transcriptionBodyPart != null) { 244 FetchProfile fetchProfile = new FetchProfile(); 245 fetchProfile.add(messageStructureWrapper.transcriptionBodyPart); 246 247 mFolder.fetch(new Message[] {messageDetails}, fetchProfile, listener); 248 } 249 250 // Found an audio attachment, this is a valid voicemail. 251 long time = messageDetails.getSentDate().getTime(); 252 String number = getNumber(messageDetails.getFrom()); 253 boolean isRead = Arrays.asList(messageDetails.getFlags()).contains(Flag.SEEN); 254 Long duration = messageDetails.getDuration(); 255 Voicemail.Builder builder = 256 Voicemail.createForInsertion(time, number) 257 .setPhoneAccount(mPhoneAccount) 258 .setSourcePackage(mContext.getPackageName()) 259 .setSourceData(messageDetails.getUid()) 260 .setIsRead(isRead) 261 .setTranscription(listener.getVoicemailTranscription()); 262 if (duration != null) { 263 builder.setDuration(duration); 264 } 265 return builder.build(); 266 } 267 268 /** 269 * The "from" field of a visual voicemail IMAP message is the number of the caller who left the 270 * message. Extract this number from the list of "from" addresses. 271 * 272 * @param fromAddresses A list of addresses that comprise the "from" line. 273 * @return The number of the voicemail sender. 274 */ getNumber(Address[] fromAddresses)275 private String getNumber(Address[] fromAddresses) { 276 if (fromAddresses != null && fromAddresses.length > 0) { 277 if (fromAddresses.length != 1) { 278 LogUtils.w(TAG, "More than one from addresses found. Using the first one."); 279 } 280 String sender = fromAddresses[0].getAddress(); 281 int atPos = sender.indexOf('@'); 282 if (atPos != -1) { 283 // Strip domain part of the address. 284 sender = sender.substring(0, atPos); 285 } 286 return sender; 287 } 288 return null; 289 } 290 291 /** 292 * Fetches the structure of the given message and returns a wrapper containing the message 293 * structure and the transcription structure (if applicable). 294 * 295 * @throws MessagingException if fetching the structure of the message fails 296 */ fetchMessageStructure(Message message)297 private MessageStructureWrapper fetchMessageStructure(Message message) throws MessagingException { 298 LogUtils.d(TAG, "Fetching message structure for " + message.getUid()); 299 300 MessageStructureFetchedListener listener = new MessageStructureFetchedListener(); 301 302 FetchProfile fetchProfile = new FetchProfile(); 303 fetchProfile.addAll( 304 Arrays.asList( 305 FetchProfile.Item.FLAGS, FetchProfile.Item.ENVELOPE, FetchProfile.Item.STRUCTURE)); 306 307 // The IMAP folder fetch method will call "messageRetrieved" on the listener when the 308 // message is successfully retrieved. 309 mFolder.fetch(new Message[] {message}, fetchProfile, listener); 310 return listener.getMessageStructure(); 311 } 312 fetchVoicemailPayload(VoicemailFetchedCallback callback, final String uid)313 public boolean fetchVoicemailPayload(VoicemailFetchedCallback callback, final String uid) { 314 try { 315 mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE); 316 if (mFolder == null) { 317 // This means we were unable to successfully open the folder. 318 return false; 319 } 320 Message message = mFolder.getMessage(uid); 321 if (message == null) { 322 return false; 323 } 324 VoicemailPayload voicemailPayload = fetchVoicemailPayload(message); 325 callback.setVoicemailContent(voicemailPayload); 326 return true; 327 } catch (MessagingException e) { 328 } finally { 329 closeImapFolder(); 330 } 331 return false; 332 } 333 334 /** 335 * Fetches the body of the given message and returns the parsed voicemail payload. 336 * 337 * @throws MessagingException if fetching the body of the message fails 338 */ fetchVoicemailPayload(Message message)339 private VoicemailPayload fetchVoicemailPayload(Message message) throws MessagingException { 340 LogUtils.d(TAG, "Fetching message body for " + message.getUid()); 341 342 MessageBodyFetchedListener listener = new MessageBodyFetchedListener(); 343 344 FetchProfile fetchProfile = new FetchProfile(); 345 fetchProfile.add(FetchProfile.Item.BODY); 346 347 mFolder.fetch(new Message[] {message}, fetchProfile, listener); 348 return listener.getVoicemailPayload(); 349 } 350 fetchTranscription(TranscriptionFetchedCallback callback, String uid)351 public boolean fetchTranscription(TranscriptionFetchedCallback callback, String uid) { 352 try { 353 mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE); 354 if (mFolder == null) { 355 // This means we were unable to successfully open the folder. 356 return false; 357 } 358 359 Message message = mFolder.getMessage(uid); 360 if (message == null) { 361 return false; 362 } 363 364 MessageStructureWrapper messageStructureWrapper = fetchMessageStructure(message); 365 if (messageStructureWrapper != null) { 366 TranscriptionFetchedListener listener = new TranscriptionFetchedListener(); 367 if (messageStructureWrapper.transcriptionBodyPart != null) { 368 FetchProfile fetchProfile = new FetchProfile(); 369 fetchProfile.add(messageStructureWrapper.transcriptionBodyPart); 370 371 // This method is called synchronously so the transcription will be populated 372 // in the listener once the next method is called. 373 mFolder.fetch(new Message[] {message}, fetchProfile, listener); 374 callback.setVoicemailTranscription(listener.getVoicemailTranscription()); 375 } 376 } 377 return true; 378 } catch (MessagingException e) { 379 LogUtils.e(TAG, e, "Messaging Exception"); 380 return false; 381 } finally { 382 closeImapFolder(); 383 } 384 } 385 386 @ChangePinResult changePin(String oldPin, String newPin)387 public int changePin(String oldPin, String newPin) throws MessagingException { 388 ImapConnection connection = mImapStore.getConnection(); 389 try { 390 String command = 391 getConfig().getProtocol().getCommand(OmtpConstants.IMAP_CHANGE_TUI_PWD_FORMAT); 392 connection.sendCommand(String.format(Locale.US, command, newPin, oldPin), true); 393 return getChangePinResultFromImapResponse(connection.readResponse()); 394 } catch (IOException ioe) { 395 VvmLog.e(TAG, "changePin: ", ioe); 396 return OmtpConstants.CHANGE_PIN_SYSTEM_ERROR; 397 } finally { 398 connection.destroyResponses(); 399 } 400 } 401 changeVoicemailTuiLanguage(String languageCode)402 public void changeVoicemailTuiLanguage(String languageCode) throws MessagingException { 403 ImapConnection connection = mImapStore.getConnection(); 404 try { 405 String command = 406 getConfig().getProtocol().getCommand(OmtpConstants.IMAP_CHANGE_VM_LANG_FORMAT); 407 connection.sendCommand(String.format(Locale.US, command, languageCode), true); 408 } catch (IOException ioe) { 409 LogUtils.e(TAG, ioe.toString()); 410 } finally { 411 connection.destroyResponses(); 412 } 413 } 414 closeNewUserTutorial()415 public void closeNewUserTutorial() throws MessagingException { 416 ImapConnection connection = mImapStore.getConnection(); 417 try { 418 String command = getConfig().getProtocol().getCommand(OmtpConstants.IMAP_CLOSE_NUT); 419 connection.executeSimpleCommand(command, false); 420 } catch (IOException ioe) { 421 throw new MessagingException(MessagingException.SERVER_ERROR, ioe.toString()); 422 } finally { 423 connection.destroyResponses(); 424 } 425 } 426 427 @ChangePinResult getChangePinResultFromImapResponse(ImapResponse response)428 private static int getChangePinResultFromImapResponse(ImapResponse response) 429 throws MessagingException { 430 if (!response.isTagged()) { 431 throw new MessagingException(MessagingException.SERVER_ERROR, "tagged response expected"); 432 } 433 if (!response.isOk()) { 434 String message = response.getStringOrEmpty(1).getString(); 435 LogUtils.d(TAG, "change PIN failed: " + message); 436 if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_SHORT.equals(message)) { 437 return OmtpConstants.CHANGE_PIN_TOO_SHORT; 438 } 439 if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_LONG.equals(message)) { 440 return OmtpConstants.CHANGE_PIN_TOO_LONG; 441 } 442 if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_WEAK.equals(message)) { 443 return OmtpConstants.CHANGE_PIN_TOO_WEAK; 444 } 445 if (OmtpConstants.RESPONSE_CHANGE_PIN_MISMATCH.equals(message)) { 446 return OmtpConstants.CHANGE_PIN_MISMATCH; 447 } 448 if (OmtpConstants.RESPONSE_CHANGE_PIN_INVALID_CHARACTER.equals(message)) { 449 return OmtpConstants.CHANGE_PIN_INVALID_CHARACTER; 450 } 451 return OmtpConstants.CHANGE_PIN_SYSTEM_ERROR; 452 } 453 LogUtils.d(TAG, "change PIN succeeded"); 454 return OmtpConstants.CHANGE_PIN_SUCCESS; 455 } 456 updateQuota()457 public void updateQuota() { 458 try { 459 mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE); 460 if (mFolder == null) { 461 // This means we were unable to successfully open the folder. 462 return; 463 } 464 updateQuota(mFolder); 465 } catch (MessagingException e) { 466 LogUtils.e(TAG, e, "Messaging Exception"); 467 } finally { 468 closeImapFolder(); 469 } 470 } 471 472 @Nullable getQuota()473 public Quota getQuota() { 474 try { 475 mFolder = openImapFolder(ImapFolder.MODE_READ_ONLY); 476 if (mFolder == null) { 477 // This means we were unable to successfully open the folder. 478 LogUtils.e(TAG, "Unable to open folder"); 479 return null; 480 } 481 return mFolder.getQuota(); 482 } catch (MessagingException e) { 483 LogUtils.e(TAG, e, "Messaging Exception"); 484 return null; 485 } finally { 486 closeImapFolder(); 487 } 488 } 489 updateQuota(ImapFolder folder)490 private void updateQuota(ImapFolder folder) throws MessagingException { 491 setQuota(folder.getQuota()); 492 } 493 setQuota(ImapFolder.Quota quota)494 private void setQuota(ImapFolder.Quota quota) { 495 if (quota == null) { 496 LogUtils.i(TAG, "quota was null"); 497 return; 498 } 499 500 LogUtils.i( 501 TAG, 502 "Updating Voicemail status table with" 503 + " quota occupied: " 504 + quota.occupied 505 + " new quota total:" 506 + quota.total); 507 VoicemailStatus.edit(mContext, mPhoneAccount).setQuota(quota.occupied, quota.total).apply(); 508 LogUtils.i(TAG, "Updated quota occupied and total"); 509 } 510 511 /** 512 * A wrapper to hold a message with its header details and the structure for transcriptions (so 513 * they can be fetched in the future). 514 */ 515 public static class MessageStructureWrapper { 516 517 public Message messageStructure; 518 public BodyPart transcriptionBodyPart; 519 MessageStructureWrapper()520 public MessageStructureWrapper() {} 521 } 522 523 /** Listener for the message structure being fetched. */ 524 private final class MessageStructureFetchedListener 525 implements ImapFolder.MessageRetrievalListener { 526 527 private MessageStructureWrapper mMessageStructure; 528 MessageStructureFetchedListener()529 public MessageStructureFetchedListener() {} 530 getMessageStructure()531 public MessageStructureWrapper getMessageStructure() { 532 return mMessageStructure; 533 } 534 535 @Override messageRetrieved(Message message)536 public void messageRetrieved(Message message) { 537 LogUtils.d(TAG, "Fetched message structure for " + message.getUid()); 538 LogUtils.d(TAG, "Message retrieved: " + message); 539 try { 540 mMessageStructure = getMessageOrNull(message); 541 if (mMessageStructure == null) { 542 LogUtils.d(TAG, "This voicemail does not have an attachment..."); 543 return; 544 } 545 } catch (MessagingException e) { 546 LogUtils.e(TAG, e, "Messaging Exception"); 547 closeImapFolder(); 548 } 549 } 550 551 /** 552 * Check if this IMAP message is a valid voicemail and whether it contains a transcription. 553 * 554 * @param message The IMAP message. 555 * @return The MessageStructureWrapper object corresponding to an IMAP message and 556 * transcription. 557 */ getMessageOrNull(Message message)558 private MessageStructureWrapper getMessageOrNull(Message message) throws MessagingException { 559 if (!message.getMimeType().startsWith("multipart/")) { 560 LogUtils.w(TAG, "Ignored non multi-part message"); 561 return null; 562 } 563 564 MessageStructureWrapper messageStructureWrapper = new MessageStructureWrapper(); 565 566 Multipart multipart = (Multipart) message.getBody(); 567 for (int i = 0; i < multipart.getCount(); ++i) { 568 BodyPart bodyPart = multipart.getBodyPart(i); 569 String bodyPartMimeType = bodyPart.getMimeType().toLowerCase(); 570 LogUtils.d(TAG, "bodyPart mime type: " + bodyPartMimeType); 571 572 if (bodyPartMimeType.startsWith("audio/")) { 573 messageStructureWrapper.messageStructure = message; 574 } else if (bodyPartMimeType.startsWith("text/")) { 575 messageStructureWrapper.transcriptionBodyPart = bodyPart; 576 } else { 577 VvmLog.v(TAG, "Unknown bodyPart MIME: " + bodyPartMimeType); 578 } 579 } 580 581 if (messageStructureWrapper.messageStructure != null) { 582 return messageStructureWrapper; 583 } 584 585 // No attachment found, this is not a voicemail. 586 return null; 587 } 588 } 589 590 /** Listener for the message body being fetched. */ 591 private final class MessageBodyFetchedListener implements ImapFolder.MessageRetrievalListener { 592 593 private VoicemailPayload mVoicemailPayload; 594 595 /** Returns the fetch voicemail payload. */ getVoicemailPayload()596 public VoicemailPayload getVoicemailPayload() { 597 return mVoicemailPayload; 598 } 599 600 @Override messageRetrieved(Message message)601 public void messageRetrieved(Message message) { 602 LogUtils.d(TAG, "Fetched message body for " + message.getUid()); 603 LogUtils.d(TAG, "Message retrieved: " + message); 604 try { 605 mVoicemailPayload = getVoicemailPayloadFromMessage(message); 606 } catch (MessagingException e) { 607 LogUtils.e(TAG, "Messaging Exception:", e); 608 } catch (IOException e) { 609 LogUtils.e(TAG, "IO Exception:", e); 610 } 611 } 612 getVoicemailPayloadFromMessage(Message message)613 private VoicemailPayload getVoicemailPayloadFromMessage(Message message) 614 throws MessagingException, IOException { 615 Multipart multipart = (Multipart) message.getBody(); 616 List<String> mimeTypes = new ArrayList<>(); 617 for (int i = 0; i < multipart.getCount(); ++i) { 618 BodyPart bodyPart = multipart.getBodyPart(i); 619 String bodyPartMimeType = bodyPart.getMimeType().toLowerCase(); 620 mimeTypes.add(bodyPartMimeType); 621 if (bodyPartMimeType.startsWith("audio/")) { 622 byte[] bytes = getDataFromBody(bodyPart.getBody()); 623 LogUtils.d(TAG, String.format("Fetched %s bytes of data", bytes.length)); 624 return new VoicemailPayload(bodyPartMimeType, bytes); 625 } 626 } 627 LogUtils.e(TAG, "No audio attachment found on this voicemail, mimeTypes:" + mimeTypes); 628 return null; 629 } 630 } 631 632 /** Listener for the transcription being fetched. */ 633 private final class TranscriptionFetchedListener implements ImapFolder.MessageRetrievalListener { 634 635 private String mVoicemailTranscription; 636 637 /** Returns the fetched voicemail transcription. */ getVoicemailTranscription()638 public String getVoicemailTranscription() { 639 return mVoicemailTranscription; 640 } 641 642 @Override messageRetrieved(Message message)643 public void messageRetrieved(Message message) { 644 LogUtils.d(TAG, "Fetched transcription for " + message.getUid()); 645 try { 646 mVoicemailTranscription = new String(getDataFromBody(message.getBody())); 647 } catch (MessagingException e) { 648 LogUtils.e(TAG, "Messaging Exception:", e); 649 } catch (IOException e) { 650 LogUtils.e(TAG, "IO Exception:", e); 651 } 652 } 653 } 654 openImapFolder(String modeReadWrite)655 private ImapFolder openImapFolder(String modeReadWrite) { 656 try { 657 if (mImapStore == null) { 658 return null; 659 } 660 ImapFolder folder = new ImapFolder(mImapStore, ImapConstants.INBOX); 661 folder.open(modeReadWrite); 662 return folder; 663 } catch (MessagingException e) { 664 LogUtils.e(TAG, e, "Messaging Exception"); 665 } 666 return null; 667 } 668 convertToImapMessages(List<Voicemail> voicemails)669 private Message[] convertToImapMessages(List<Voicemail> voicemails) { 670 Message[] messages = new Message[voicemails.size()]; 671 for (int i = 0; i < voicemails.size(); ++i) { 672 messages[i] = new MimeMessage(); 673 messages[i].setUid(voicemails.get(i).getSourceData()); 674 } 675 return messages; 676 } 677 closeImapFolder()678 private void closeImapFolder() { 679 if (mFolder != null) { 680 mFolder.close(true); 681 } 682 } 683 getDataFromBody(Body body)684 private byte[] getDataFromBody(Body body) throws IOException, MessagingException { 685 ByteArrayOutputStream out = new ByteArrayOutputStream(); 686 BufferedOutputStream bufferedOut = new BufferedOutputStream(out); 687 try { 688 body.writeTo(bufferedOut); 689 return Base64.decode(out.toByteArray(), Base64.DEFAULT); 690 } finally { 691 IOUtils.closeQuietly(bufferedOut); 692 IOUtils.closeQuietly(out); 693 } 694 } 695 } 696