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