@Override public void loadMore(long messageId) throws RemoteException { /// M: We Can't load more in low storage state @{ if (StorageLowState.checkIfStorageLow(mContext)) { LogUtils.e(Logging.LOG_TAG, "Can't load more due to low storage"); return; } /// @} // Load a message for view... try { // 1. Resample the message, in case it disappeared or synced while // this command was in queue final EmailContent.Message message = EmailContent.Message.restoreMessageWithId(mContext, messageId); if (message == null) { return; } if (message.mFlagLoaded == EmailContent.Message.FLAG_LOADED_COMPLETE) { // We should NEVER get here return; } // 2. Open the remote folder. // TODO combine with common code in loadAttachment final Account account = Account.restoreAccountWithId(mContext, message.mAccountKey); final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey); if (account == null || mailbox == null) { // mListeners.loadMessageForViewFailed(messageId, "null account or mailbox"); return; } TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, account)); final Store remoteStore = Store.getInstance(account, mContext); final String remoteServerId; // If this is a search result, use the protocolSearchInfo field to get the // correct remote location if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) { remoteServerId = message.mProtocolSearchInfo; } else { remoteServerId = mailbox.mServerId; } final Folder remoteFolder = remoteStore.getFolder(remoteServerId); remoteFolder.open(OpenMode.READ_WRITE); // 3. Set up to download the entire message final Message remoteMessage = remoteFolder.getMessage(message.mServerId); final FetchProfile fp = new FetchProfile(); fp.add(FetchProfile.Item.BODY); remoteFolder.fetch(new Message[] {remoteMessage}, fp, null); // 4. Write to provider Utilities.copyOneMessageToProvider( mContext, remoteMessage, account, mailbox, EmailContent.Message.FLAG_LOADED_COMPLETE); } catch (MessagingException me) { if (Logging.LOGD) LogUtils.v(Logging.LOG_TAG, "", me); } catch (RuntimeException rte) { LogUtils.d(Logging.LOG_TAG, "RTE During loadMore"); } }
@Deprecated @Override public void startSync(long mailboxId, boolean userRequest, int deltaMessageCount) throws RemoteException { final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mailboxId); if (mailbox == null) return; final Account account = Account.restoreAccountWithId(mContext, mailbox.mAccountKey); if (account == null) return; final EmailServiceInfo info = EmailServiceUtils.getServiceInfoForAccount(mContext, account.mId); final android.accounts.Account acct = new android.accounts.Account(account.mEmailAddress, info.accountType); final Bundle extras = Mailbox.createSyncBundle(mailboxId); if (userRequest) { extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true); extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); } if (deltaMessageCount != 0) { extras.putInt(Mailbox.SYNC_EXTRA_DELTA_MESSAGE_COUNT, deltaMessageCount); } ContentResolver.requestSync(acct, EmailContent.AUTHORITY, extras); LogUtils.i( Logging.LOG_TAG, "requestSync EmailServiceStub startSync %s, %s", account.toString(), extras.toString()); }
/** * This is the remote call from the Email app, currently unused. TODO: remove this when it's * been deleted from IEmailService.aidl. */ @Deprecated @Override public void startSync(long mailboxId, boolean userRequest, int deltaMessageCount) throws RemoteException { SyncManager exchangeService = INSTANCE; if (exchangeService == null) return; checkExchangeServiceServiceRunning(); Mailbox m = Mailbox.restoreMailboxWithId(exchangeService, mailboxId); if (m == null) return; Account acct = Account.restoreAccountWithId(exchangeService, m.mAccountKey); if (acct == null) return; // If this is a user request and we're being held, release the hold; this allows us to // try again (the hold might have been specific to this account and released already) if (userRequest) { if (onSyncDisabledHold(acct)) { releaseSyncHolds(exchangeService, AbstractSyncService.EXIT_ACCESS_DENIED, acct); log("User requested sync of account in sync disabled hold; releasing"); } else if (onSecurityHold(acct)) { releaseSyncHolds(exchangeService, AbstractSyncService.EXIT_SECURITY_FAILURE, acct); log("User requested sync of account in security hold; releasing"); } if (sConnectivityHold) { return; } } if (m.mType == Mailbox.TYPE_OUTBOX) { // We're using SERVER_ID to indicate an error condition (it has no other use for // sent mail) Upon request to sync the Outbox, we clear this so that all messages // are candidates for sending. ContentValues cv = new ContentValues(); cv.put(SyncColumns.SERVER_ID, 0); exchangeService .getContentResolver() .update( Message.CONTENT_URI, cv, WHERE_MAILBOX_KEY, new String[] {Long.toString(mailboxId)}); // Clear the error state; the Outbox sync will be started from checkMailboxes exchangeService.mSyncErrorMap.remove(mailboxId); kick("start outbox"); // Outbox can't be synced in EAS return; } else if (!isSyncable(m)) { return; } startManualSync( mailboxId, userRequest ? ExchangeService.SYNC_UI_REQUEST : ExchangeService.SYNC_SERVICE_START_SYNC, null); }
@Override public void onSelected(Account account, long mailboxId) { String shortcutName; /* M: Get display name from FolderProperty, not from database. * Aiming to accomplish multi-language.*/ Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mailboxId); if (Account.isNormalAccount(account.mId) && (mailbox.mType != Mailbox.TYPE_INBOX)) { shortcutName = FolderProperties.getInstance(this).getDisplayName(mailbox); } else { shortcutName = account.getDisplayName(); } setupShortcut(account, mailboxId, shortcutName); finish(); }
@Override public void hostChanged(long accountId) throws RemoteException { SyncManager exchangeService = INSTANCE; if (exchangeService == null) return; ConcurrentHashMap<Long, SyncError> syncErrorMap = exchangeService.mSyncErrorMap; // Go through the various error mailboxes for (long mailboxId : syncErrorMap.keySet()) { SyncError error = syncErrorMap.get(mailboxId); // If it's a login failure, look a little harder Mailbox m = Mailbox.restoreMailboxWithId(exchangeService, mailboxId); // If it's for the account whose host has changed, clear the error // If the mailbox is no longer around, remove the entry in the map if (m == null) { syncErrorMap.remove(mailboxId); } else if (error != null && m.mAccountKey == accountId) { error.fatal = false; error.holdEndTime = 0; } } // Stop any running syncs exchangeService.stopAccountSyncs(accountId, true); // Kick ExchangeService kick("host changed"); }
@Override public void loadAttachment( final IEmailServiceCallback cb, final long attachmentId, final boolean background) throws RemoteException { /// M: We Can't load attachment in low storage state @{ if (StorageLowState.checkIfStorageLow(mContext)) { LogUtils.e(Logging.LOG_TAG, "Can't load attachment due to low storage"); cb.loadAttachmentStatus(0, attachmentId, EmailServiceStatus.SUCCESS, 0); return; } /// @} Folder remoteFolder = null; try { // 1. Check if the attachment is already here and return early in that case Attachment attachment = Attachment.restoreAttachmentWithId(mContext, attachmentId); if (attachment == null) { cb.loadAttachmentStatus(0, attachmentId, EmailServiceStatus.ATTACHMENT_NOT_FOUND, 0); return; } final long messageId = attachment.mMessageKey; final EmailContent.Message message = EmailContent.Message.restoreMessageWithId(mContext, attachment.mMessageKey); if (message == null) { cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.MESSAGE_NOT_FOUND, 0); return; } // If the message is loaded, just report that we're finished if (Utility.attachmentExists(mContext, attachment) && attachment.mUiState == UIProvider.AttachmentState.SAVED) { cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.SUCCESS, 0); return; } // Say we're starting... cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.IN_PROGRESS, 0); // 2. Open the remote folder. final Account account = Account.restoreAccountWithId(mContext, message.mAccountKey); Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey); if (mailbox.mType == Mailbox.TYPE_OUTBOX /// M: View an attachment which comes from refMessage need sourceKey to identify || mailbox.mType == Mailbox.TYPE_DRAFTS) { long sourceId = Utility.getFirstRowLong( mContext, Body.CONTENT_URI, new String[] {BodyColumns.SOURCE_MESSAGE_KEY}, BodyColumns.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null, 0, -1L); if (sourceId != -1) { EmailContent.Message sourceMsg = EmailContent.Message.restoreMessageWithId(mContext, sourceId); if (sourceMsg != null) { mailbox = Mailbox.restoreMailboxWithId(mContext, sourceMsg.mMailboxKey); message.mServerId = sourceMsg.mServerId; } } } else if (mailbox.mType == Mailbox.TYPE_SEARCH && message.mMainMailboxKey != 0) { mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMainMailboxKey); } if (account == null || mailbox == null) { // If the account/mailbox are gone, just report success; the UI handles this cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.SUCCESS, 0); return; } TrafficStats.setThreadStatsTag(TrafficFlags.getAttachmentFlags(mContext, account)); final Store remoteStore = Store.getInstance(account, mContext); remoteFolder = remoteStore.getFolder(mailbox.mServerId); remoteFolder.open(OpenMode.READ_WRITE); // 3. Generate a shell message in which to retrieve the attachment, // and a shell BodyPart for the attachment. Then glue them together. final Message storeMessage = remoteFolder.createMessage(message.mServerId); final MimeBodyPart storePart = new MimeBodyPart(); storePart.setSize((int) attachment.mSize); storePart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, attachment.mLocation); storePart.setHeader( MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n name=\"%s\"", attachment.mMimeType, attachment.mFileName)); // TODO is this always true for attachments? I think we dropped the // true encoding along the way /// M: set encoding type according to data base record. String encoding = attachment.mEncoding; if (TextUtils.isEmpty(encoding)) { encoding = "base64"; } storePart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding); final MimeMultipart multipart = new MimeMultipart(); multipart.setSubType("mixed"); multipart.addBodyPart(storePart); storeMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "multipart/mixed"); storeMessage.setBody(multipart); // 4. Now ask for the attachment to be fetched final FetchProfile fp = new FetchProfile(); fp.add(storePart); remoteFolder.fetch( new Message[] {storeMessage}, fp, new MessageRetrievalListenerBridge(messageId, attachmentId, cb)); // If we failed to load the attachment, throw an Exception here, so that // AttachmentDownloadService knows that we failed if (storePart.getBody() == null) { throw new MessagingException("Attachment not loaded."); } // Save the attachment to wherever it's going AttachmentUtilities.saveAttachment( mContext, storePart.getBody().getInputStream(), attachment); // 6. Report success cb.loadAttachmentStatus(messageId, attachmentId, EmailServiceStatus.SUCCESS, 0); } catch (MessagingException me) { LogUtils.i(Logging.LOG_TAG, me, "Error loading attachment"); final ContentValues cv = new ContentValues(1); cv.put(AttachmentColumns.UI_STATE, UIProvider.AttachmentState.FAILED); final Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, attachmentId); mContext.getContentResolver().update(uri, cv, null, null); cb.loadAttachmentStatus(0, attachmentId, EmailServiceStatus.CONNECTION_ERROR, 0); } finally { if (remoteFolder != null) { remoteFolder.close(false); } } }
public static int searchMessages( Context context, long accountId, SearchParams searchParams, long destMailboxId) { // Sanity check for arguments final int offset = searchParams.mOffset; final int limit = searchParams.mLimit; final String filter = searchParams.mFilter; if (limit < 0 || limit > MAX_SEARCH_RESULTS || offset < 0) return 0; // TODO Should this be checked in UI? Are there guidelines for minimums? if (filter == null || filter.length() < MIN_QUERY_LENGTH) return 0; int res = 0; final Account account = Account.restoreAccountWithId(context, accountId); if (account == null) return res; final EasSyncService svc = EasSyncService.setupServiceForAccount(context, account); if (svc == null) return res; final Mailbox searchMailbox = Mailbox.restoreMailboxWithId(context, destMailboxId); // Sanity check; account might have been deleted? if (searchMailbox == null) return res; final ContentValues statusValues = new ContentValues(2); try { // Set the status of this mailbox to indicate query statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.LIVE_QUERY); searchMailbox.update(context, statusValues); svc.mMailbox = searchMailbox; svc.mAccount = account; final Serializer s = new Serializer(); s.start(Tags.SEARCH_SEARCH).start(Tags.SEARCH_STORE); s.data(Tags.SEARCH_NAME, "Mailbox"); s.start(Tags.SEARCH_QUERY).start(Tags.SEARCH_AND); s.data(Tags.SYNC_CLASS, "Email"); // If this isn't an inbox search, then include the collection id final Mailbox inbox = Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_INBOX); if (inbox == null) return 0; if (searchParams.mMailboxId != inbox.mId) { s.data(Tags.SYNC_COLLECTION_ID, inbox.mServerId); } s.data(Tags.SEARCH_FREE_TEXT, filter); // Add the date window if appropriate if (searchParams.mStartDate != null) { s.start(Tags.SEARCH_GREATER_THAN); s.tag(Tags.EMAIL_DATE_RECEIVED); s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(searchParams.mStartDate)); s.end(); // SEARCH_GREATER_THAN } if (searchParams.mEndDate != null) { s.start(Tags.SEARCH_LESS_THAN); s.tag(Tags.EMAIL_DATE_RECEIVED); s.data(Tags.SEARCH_VALUE, Eas.DATE_FORMAT.format(searchParams.mEndDate)); s.end(); // SEARCH_LESS_THAN } s.end().end(); // SEARCH_AND, SEARCH_QUERY s.start(Tags.SEARCH_OPTIONS); if (offset == 0) { s.tag(Tags.SEARCH_REBUILD_RESULTS); } if (searchParams.mIncludeChildren) { s.tag(Tags.SEARCH_DEEP_TRAVERSAL); } // Range is sent in the form first-last (e.g. 0-9) s.data(Tags.SEARCH_RANGE, offset + "-" + (offset + limit - 1)); s.start(Tags.BASE_BODY_PREFERENCE); s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML); s.data(Tags.BASE_TRUNCATION_SIZE, "20000"); s.end(); // BASE_BODY_PREFERENCE s.end().end().end().done(); // SEARCH_OPTIONS, SEARCH_STORE, SEARCH_SEARCH final EasResponse resp = svc.sendHttpClientPost("Search", s.toByteArray()); try { final int code = resp.getStatus(); if (code == HttpStatus.SC_OK) { final InputStream is = resp.getInputStream(); try { final SearchParser sp = new SearchParser(is, svc, filter); sp.parse(); res = sp.getTotalResults(); /** M: Set and update mailbox flag. @{ */ int currentCount = offset + sp.getCurrentResults(); boolean allMessagesLoaded = false; if (currentCount >= res) { allMessagesLoaded = true; } searchMailbox.updateAllMessageDownloadFlag(context, allMessagesLoaded); /** @} */ } finally { is.close(); } } else { svc.userLog("Search returned " + code); } } finally { resp.close(); } } catch (IOException e) { svc.userLog("Search exception " + e); } finally { // TODO: Handle error states // Set the status of this mailbox to indicate query over statusValues.put(Mailbox.SYNC_TIME, System.currentTimeMillis()); statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC); searchMailbox.update(context, statusValues); } // Return the total count return res; }