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 83 public InitializingException(String message) { 84 super(message); 85 } 86 } 87 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 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 138 public void close() { 139 mImapStore.closeConnection(); 140 } 141 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 152 public OmtpVvmCarrierConfigHelper getConfig() { 153 return mConfig; 154 } 155 156 public ImapConnection connect() { 157 return mImapStore.getConnection(); 158 } 159 160 /** The caller thread will block until the method returns. */ 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. */ 166 public boolean markMessagesAsDeleted(List<Voicemail> voicemails) { 167 return setFlags(voicemails, Flag.DELETED); 168 } 169 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 */ 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 */ 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 */ 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 return Voicemail.createForInsertion(time, number) 255 .setPhoneAccount(mPhoneAccount) 256 .setSourcePackage(mContext.getPackageName()) 257 .setSourceData(messageDetails.getUid()) 258 .setIsRead(isRead) 259 .setTranscription(listener.getVoicemailTranscription()) 260 .build(); 261 } 262 263 /** 264 * The "from" field of a visual voicemail IMAP message is the number of the caller who left the 265 * message. Extract this number from the list of "from" addresses. 266 * 267 * @param fromAddresses A list of addresses that comprise the "from" line. 268 * @return The number of the voicemail sender. 269 */ 270 private String getNumber(Address[] fromAddresses) { 271 if (fromAddresses != null && fromAddresses.length > 0) { 272 if (fromAddresses.length != 1) { 273 LogUtils.w(TAG, "More than one from addresses found. Using the first one."); 274 } 275 String sender = fromAddresses[0].getAddress(); 276 int atPos = sender.indexOf('@'); 277 if (atPos != -1) { 278 // Strip domain part of the address. 279 sender = sender.substring(0, atPos); 280 } 281 return sender; 282 } 283 return null; 284 } 285 286 /** 287 * Fetches the structure of the given message and returns a wrapper containing the message 288 * structure and the transcription structure (if applicable). 289 * 290 * @throws MessagingException if fetching the structure of the message fails 291 */ 292 private MessageStructureWrapper fetchMessageStructure(Message message) throws MessagingException { 293 LogUtils.d(TAG, "Fetching message structure for " + message.getUid()); 294 295 MessageStructureFetchedListener listener = new MessageStructureFetchedListener(); 296 297 FetchProfile fetchProfile = new FetchProfile(); 298 fetchProfile.addAll( 299 Arrays.asList( 300 FetchProfile.Item.FLAGS, FetchProfile.Item.ENVELOPE, FetchProfile.Item.STRUCTURE)); 301 302 // The IMAP folder fetch method will call "messageRetrieved" on the listener when the 303 // message is successfully retrieved. 304 mFolder.fetch(new Message[] {message}, fetchProfile, listener); 305 return listener.getMessageStructure(); 306 } 307 308 public boolean fetchVoicemailPayload(VoicemailFetchedCallback callback, final String uid) { 309 try { 310 mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE); 311 if (mFolder == null) { 312 // This means we were unable to successfully open the folder. 313 return false; 314 } 315 Message message = mFolder.getMessage(uid); 316 if (message == null) { 317 return false; 318 } 319 VoicemailPayload voicemailPayload = fetchVoicemailPayload(message); 320 callback.setVoicemailContent(voicemailPayload); 321 return true; 322 } catch (MessagingException e) { 323 } finally { 324 closeImapFolder(); 325 } 326 return false; 327 } 328 329 /** 330 * Fetches the body of the given message and returns the parsed voicemail payload. 331 * 332 * @throws MessagingException if fetching the body of the message fails 333 */ 334 private VoicemailPayload fetchVoicemailPayload(Message message) throws MessagingException { 335 LogUtils.d(TAG, "Fetching message body for " + message.getUid()); 336 337 MessageBodyFetchedListener listener = new MessageBodyFetchedListener(); 338 339 FetchProfile fetchProfile = new FetchProfile(); 340 fetchProfile.add(FetchProfile.Item.BODY); 341 342 mFolder.fetch(new Message[] {message}, fetchProfile, listener); 343 return listener.getVoicemailPayload(); 344 } 345 346 public boolean fetchTranscription(TranscriptionFetchedCallback callback, String uid) { 347 try { 348 mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE); 349 if (mFolder == null) { 350 // This means we were unable to successfully open the folder. 351 return false; 352 } 353 354 Message message = mFolder.getMessage(uid); 355 if (message == null) { 356 return false; 357 } 358 359 MessageStructureWrapper messageStructureWrapper = fetchMessageStructure(message); 360 if (messageStructureWrapper != null) { 361 TranscriptionFetchedListener listener = new TranscriptionFetchedListener(); 362 if (messageStructureWrapper.transcriptionBodyPart != null) { 363 FetchProfile fetchProfile = new FetchProfile(); 364 fetchProfile.add(messageStructureWrapper.transcriptionBodyPart); 365 366 // This method is called synchronously so the transcription will be populated 367 // in the listener once the next method is called. 368 mFolder.fetch(new Message[] {message}, fetchProfile, listener); 369 callback.setVoicemailTranscription(listener.getVoicemailTranscription()); 370 } 371 } 372 return true; 373 } catch (MessagingException e) { 374 LogUtils.e(TAG, e, "Messaging Exception"); 375 return false; 376 } finally { 377 closeImapFolder(); 378 } 379 } 380 381 @ChangePinResult 382 public int changePin(String oldPin, String newPin) throws MessagingException { 383 ImapConnection connection = mImapStore.getConnection(); 384 try { 385 String command = 386 getConfig().getProtocol().getCommand(OmtpConstants.IMAP_CHANGE_TUI_PWD_FORMAT); 387 connection.sendCommand(String.format(Locale.US, command, newPin, oldPin), true); 388 return getChangePinResultFromImapResponse(connection.readResponse()); 389 } catch (IOException ioe) { 390 VvmLog.e(TAG, "changePin: ", ioe); 391 return OmtpConstants.CHANGE_PIN_SYSTEM_ERROR; 392 } finally { 393 connection.destroyResponses(); 394 } 395 } 396 397 public void changeVoicemailTuiLanguage(String languageCode) throws MessagingException { 398 ImapConnection connection = mImapStore.getConnection(); 399 try { 400 String command = 401 getConfig().getProtocol().getCommand(OmtpConstants.IMAP_CHANGE_VM_LANG_FORMAT); 402 connection.sendCommand(String.format(Locale.US, command, languageCode), true); 403 } catch (IOException ioe) { 404 LogUtils.e(TAG, ioe.toString()); 405 } finally { 406 connection.destroyResponses(); 407 } 408 } 409 410 public void closeNewUserTutorial() throws MessagingException { 411 ImapConnection connection = mImapStore.getConnection(); 412 try { 413 String command = getConfig().getProtocol().getCommand(OmtpConstants.IMAP_CLOSE_NUT); 414 connection.executeSimpleCommand(command, false); 415 } catch (IOException ioe) { 416 throw new MessagingException(MessagingException.SERVER_ERROR, ioe.toString()); 417 } finally { 418 connection.destroyResponses(); 419 } 420 } 421 422 @ChangePinResult 423 private static int getChangePinResultFromImapResponse(ImapResponse response) 424 throws MessagingException { 425 if (!response.isTagged()) { 426 throw new MessagingException(MessagingException.SERVER_ERROR, "tagged response expected"); 427 } 428 if (!response.isOk()) { 429 String message = response.getStringOrEmpty(1).getString(); 430 LogUtils.d(TAG, "change PIN failed: " + message); 431 if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_SHORT.equals(message)) { 432 return OmtpConstants.CHANGE_PIN_TOO_SHORT; 433 } 434 if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_LONG.equals(message)) { 435 return OmtpConstants.CHANGE_PIN_TOO_LONG; 436 } 437 if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_WEAK.equals(message)) { 438 return OmtpConstants.CHANGE_PIN_TOO_WEAK; 439 } 440 if (OmtpConstants.RESPONSE_CHANGE_PIN_MISMATCH.equals(message)) { 441 return OmtpConstants.CHANGE_PIN_MISMATCH; 442 } 443 if (OmtpConstants.RESPONSE_CHANGE_PIN_INVALID_CHARACTER.equals(message)) { 444 return OmtpConstants.CHANGE_PIN_INVALID_CHARACTER; 445 } 446 return OmtpConstants.CHANGE_PIN_SYSTEM_ERROR; 447 } 448 LogUtils.d(TAG, "change PIN succeeded"); 449 return OmtpConstants.CHANGE_PIN_SUCCESS; 450 } 451 452 public void updateQuota() { 453 try { 454 mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE); 455 if (mFolder == null) { 456 // This means we were unable to successfully open the folder. 457 return; 458 } 459 updateQuota(mFolder); 460 } catch (MessagingException e) { 461 LogUtils.e(TAG, e, "Messaging Exception"); 462 } finally { 463 closeImapFolder(); 464 } 465 } 466 467 @Nullable 468 public Quota getQuota() { 469 try { 470 mFolder = openImapFolder(ImapFolder.MODE_READ_ONLY); 471 if (mFolder == null) { 472 // This means we were unable to successfully open the folder. 473 LogUtils.e(TAG, "Unable to open folder"); 474 return null; 475 } 476 return mFolder.getQuota(); 477 } catch (MessagingException e) { 478 LogUtils.e(TAG, e, "Messaging Exception"); 479 return null; 480 } finally { 481 closeImapFolder(); 482 } 483 } 484 485 private void updateQuota(ImapFolder folder) throws MessagingException { 486 setQuota(folder.getQuota()); 487 } 488 489 private void setQuota(ImapFolder.Quota quota) { 490 if (quota == null) { 491 LogUtils.i(TAG, "quota was null"); 492 return; 493 } 494 495 LogUtils.i( 496 TAG, 497 "Updating Voicemail status table with" 498 + " quota occupied: " 499 + quota.occupied 500 + " new quota total:" 501 + quota.total); 502 VoicemailStatus.edit(mContext, mPhoneAccount).setQuota(quota.occupied, quota.total).apply(); 503 LogUtils.i(TAG, "Updated quota occupied and total"); 504 } 505 506 /** 507 * A wrapper to hold a message with its header details and the structure for transcriptions (so 508 * they can be fetched in the future). 509 */ 510 public static class MessageStructureWrapper { 511 512 public Message messageStructure; 513 public BodyPart transcriptionBodyPart; 514 515 public MessageStructureWrapper() {} 516 } 517 518 /** Listener for the message structure being fetched. */ 519 private final class MessageStructureFetchedListener 520 implements ImapFolder.MessageRetrievalListener { 521 522 private MessageStructureWrapper mMessageStructure; 523 524 public MessageStructureFetchedListener() {} 525 526 public MessageStructureWrapper getMessageStructure() { 527 return mMessageStructure; 528 } 529 530 @Override 531 public void messageRetrieved(Message message) { 532 LogUtils.d(TAG, "Fetched message structure for " + message.getUid()); 533 LogUtils.d(TAG, "Message retrieved: " + message); 534 try { 535 mMessageStructure = getMessageOrNull(message); 536 if (mMessageStructure == null) { 537 LogUtils.d(TAG, "This voicemail does not have an attachment..."); 538 return; 539 } 540 } catch (MessagingException e) { 541 LogUtils.e(TAG, e, "Messaging Exception"); 542 closeImapFolder(); 543 } 544 } 545 546 /** 547 * Check if this IMAP message is a valid voicemail and whether it contains a transcription. 548 * 549 * @param message The IMAP message. 550 * @return The MessageStructureWrapper object corresponding to an IMAP message and 551 * transcription. 552 */ 553 private MessageStructureWrapper getMessageOrNull(Message message) throws MessagingException { 554 if (!message.getMimeType().startsWith("multipart/")) { 555 LogUtils.w(TAG, "Ignored non multi-part message"); 556 return null; 557 } 558 559 MessageStructureWrapper messageStructureWrapper = new MessageStructureWrapper(); 560 561 Multipart multipart = (Multipart) message.getBody(); 562 for (int i = 0; i < multipart.getCount(); ++i) { 563 BodyPart bodyPart = multipart.getBodyPart(i); 564 String bodyPartMimeType = bodyPart.getMimeType().toLowerCase(); 565 LogUtils.d(TAG, "bodyPart mime type: " + bodyPartMimeType); 566 567 if (bodyPartMimeType.startsWith("audio/")) { 568 messageStructureWrapper.messageStructure = message; 569 } else if (bodyPartMimeType.startsWith("text/")) { 570 messageStructureWrapper.transcriptionBodyPart = bodyPart; 571 } else { 572 VvmLog.v(TAG, "Unknown bodyPart MIME: " + bodyPartMimeType); 573 } 574 } 575 576 if (messageStructureWrapper.messageStructure != null) { 577 return messageStructureWrapper; 578 } 579 580 // No attachment found, this is not a voicemail. 581 return null; 582 } 583 } 584 585 /** Listener for the message body being fetched. */ 586 private final class MessageBodyFetchedListener implements ImapFolder.MessageRetrievalListener { 587 588 private VoicemailPayload mVoicemailPayload; 589 590 /** Returns the fetch voicemail payload. */ 591 public VoicemailPayload getVoicemailPayload() { 592 return mVoicemailPayload; 593 } 594 595 @Override 596 public void messageRetrieved(Message message) { 597 LogUtils.d(TAG, "Fetched message body for " + message.getUid()); 598 LogUtils.d(TAG, "Message retrieved: " + message); 599 try { 600 mVoicemailPayload = getVoicemailPayloadFromMessage(message); 601 } catch (MessagingException e) { 602 LogUtils.e(TAG, "Messaging Exception:", e); 603 } catch (IOException e) { 604 LogUtils.e(TAG, "IO Exception:", e); 605 } 606 } 607 608 private VoicemailPayload getVoicemailPayloadFromMessage(Message message) 609 throws MessagingException, IOException { 610 Multipart multipart = (Multipart) message.getBody(); 611 List<String> mimeTypes = new ArrayList<>(); 612 for (int i = 0; i < multipart.getCount(); ++i) { 613 BodyPart bodyPart = multipart.getBodyPart(i); 614 String bodyPartMimeType = bodyPart.getMimeType().toLowerCase(); 615 mimeTypes.add(bodyPartMimeType); 616 if (bodyPartMimeType.startsWith("audio/")) { 617 byte[] bytes = getDataFromBody(bodyPart.getBody()); 618 LogUtils.d(TAG, String.format("Fetched %s bytes of data", bytes.length)); 619 return new VoicemailPayload(bodyPartMimeType, bytes); 620 } 621 } 622 LogUtils.e(TAG, "No audio attachment found on this voicemail, mimeTypes:" + mimeTypes); 623 return null; 624 } 625 } 626 627 /** Listener for the transcription being fetched. */ 628 private final class TranscriptionFetchedListener implements ImapFolder.MessageRetrievalListener { 629 630 private String mVoicemailTranscription; 631 632 /** Returns the fetched voicemail transcription. */ 633 public String getVoicemailTranscription() { 634 return mVoicemailTranscription; 635 } 636 637 @Override 638 public void messageRetrieved(Message message) { 639 LogUtils.d(TAG, "Fetched transcription for " + message.getUid()); 640 try { 641 mVoicemailTranscription = new String(getDataFromBody(message.getBody())); 642 } catch (MessagingException e) { 643 LogUtils.e(TAG, "Messaging Exception:", e); 644 } catch (IOException e) { 645 LogUtils.e(TAG, "IO Exception:", e); 646 } 647 } 648 } 649 650 private ImapFolder openImapFolder(String modeReadWrite) { 651 try { 652 if (mImapStore == null) { 653 return null; 654 } 655 ImapFolder folder = new ImapFolder(mImapStore, ImapConstants.INBOX); 656 folder.open(modeReadWrite); 657 return folder; 658 } catch (MessagingException e) { 659 LogUtils.e(TAG, e, "Messaging Exception"); 660 } 661 return null; 662 } 663 664 private Message[] convertToImapMessages(List<Voicemail> voicemails) { 665 Message[] messages = new Message[voicemails.size()]; 666 for (int i = 0; i < voicemails.size(); ++i) { 667 messages[i] = new MimeMessage(); 668 messages[i].setUid(voicemails.get(i).getSourceData()); 669 } 670 return messages; 671 } 672 673 private void closeImapFolder() { 674 if (mFolder != null) { 675 mFolder.close(true); 676 } 677 } 678 679 private byte[] getDataFromBody(Body body) throws IOException, MessagingException { 680 ByteArrayOutputStream out = new ByteArrayOutputStream(); 681 BufferedOutputStream bufferedOut = new BufferedOutputStream(out); 682 try { 683 body.writeTo(bufferedOut); 684 return Base64.decode(out.toByteArray(), Base64.DEFAULT); 685 } finally { 686 IOUtils.closeQuietly(bufferedOut); 687 IOUtils.closeQuietly(out); 688 } 689 } 690 } 691