| /* |
| * Copyright (C) 2014 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.email.provider; |
| |
| import android.content.ContentResolver; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.database.Cursor; |
| import android.database.CursorWrapper; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.text.TextUtils; |
| import android.text.format.DateUtils; |
| import android.text.util.Rfc822Token; |
| import android.text.util.Rfc822Tokenizer; |
| |
| import com.android.emailcommon.Logging; |
| import com.android.emailcommon.mail.Address; |
| import com.android.emailcommon.provider.EmailContent; |
| import com.android.emailcommon.provider.Mailbox; |
| import com.android.mail.browse.ConversationCursorOperationListener; |
| import com.android.mail.providers.ConversationInfo; |
| import com.android.mail.providers.Folder; |
| import com.android.mail.providers.FolderList; |
| import com.android.mail.providers.ParticipantInfo; |
| import com.android.mail.providers.UIProvider; |
| import com.android.mail.providers.UIProvider.ConversationColumns; |
| import com.android.mail.utils.LogUtils; |
| import com.google.common.collect.Lists; |
| |
| /** |
| * Wrapper that handles the visibility feature (i.e. the conversation list is visible, so |
| * any pending notifications for the corresponding mailbox should be canceled). We also handle |
| * getExtras() to provide a snapshot of the mailbox's status |
| */ |
| public class EmailConversationCursor extends CursorWrapper implements |
| ConversationCursorOperationListener { |
| private final long mMailboxId; |
| private final int mMailboxTypeId; |
| private final Context mContext; |
| private final FolderList mFolderList; |
| private final Bundle mExtras = new Bundle(); |
| |
| /** |
| * When showing a folder, if it's been at least this long since the last sync, |
| * force a folder refresh. |
| */ |
| private static final long AUTO_REFRESH_INTERVAL_MS = 5 * DateUtils.MINUTE_IN_MILLIS; |
| |
| public EmailConversationCursor(final Context context, final Cursor cursor, |
| final Folder folder, final long mailboxId) { |
| super(cursor); |
| mMailboxId = mailboxId; |
| mContext = context; |
| mFolderList = FolderList.copyOf(Lists.newArrayList(folder)); |
| Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId); |
| |
| if (mailbox != null) { |
| mMailboxTypeId = mailbox.mType; |
| |
| mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_TOTAL_COUNT, mailbox.mTotalCount); |
| if (mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_BACKGROUND |
| || mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_USER |
| || mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_LIVE |
| || mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_INITIAL_SYNC_NEEDED) { |
| mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS, |
| UIProvider.CursorStatus.LOADING); |
| } else if (mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_NONE) { |
| if (mailbox.mSyncInterval == 0 |
| && (Mailbox.isSyncableType(mailbox.mType) |
| || mailbox.mType == Mailbox.TYPE_SEARCH) |
| && !TextUtils.isEmpty(mailbox.mServerId) && |
| // TODO: There's potentially a race condition here. |
| // Consider merging this check with the auto-sync code in respond. |
| System.currentTimeMillis() - mailbox.mSyncTime |
| > AUTO_REFRESH_INTERVAL_MS) { |
| // This will be syncing momentarily |
| mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS, |
| UIProvider.CursorStatus.LOADING); |
| } else { |
| mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS, |
| UIProvider.CursorStatus.COMPLETE); |
| } |
| } else { |
| LogUtils.d(Logging.LOG_TAG, |
| "Unknown mailbox sync status" + mailbox.mUiSyncStatus); |
| mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS, |
| UIProvider.CursorStatus.COMPLETE); |
| } |
| } else { |
| mMailboxTypeId = -1; |
| // TODO for virtual mailboxes, we may want to do something besides just fake it |
| mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_TOTAL_COUNT, |
| cursor != null ? cursor.getCount() : 0); |
| mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS, |
| UIProvider.CursorStatus.COMPLETE); |
| } |
| } |
| |
| @Override |
| public Bundle getExtras() { |
| return mExtras; |
| } |
| |
| @Override |
| public Bundle respond(Bundle params) { |
| final String setVisibilityKey = |
| UIProvider.ConversationCursorCommand.COMMAND_KEY_SET_VISIBILITY; |
| if (params.containsKey(setVisibilityKey)) { |
| final boolean visible = params.getBoolean(setVisibilityKey); |
| if (visible) { |
| // Mark all messages as seen |
| markContentsSeen(); |
| if (params.containsKey( |
| UIProvider.ConversationCursorCommand.COMMAND_KEY_ENTERED_FOLDER)) { |
| Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mMailboxId); |
| if (mailbox != null) { |
| // For non-push mailboxes, if it's stale (i.e. last sync was a while |
| // ago), force a sync. |
| // TODO: Fix the check for whether we're non-push? Right now it checks |
| // whether we are participating in account sync rules. |
| if (mailbox.mSyncInterval == 0) { |
| final long timeSinceLastSync = |
| System.currentTimeMillis() - mailbox.mSyncTime; |
| if (timeSinceLastSync > AUTO_REFRESH_INTERVAL_MS) { |
| final ContentResolver resolver = mContext.getContentResolver(); |
| final Uri refreshUri = Uri.parse(EmailContent.CONTENT_URI + |
| "/" + EmailProvider.QUERY_UIREFRESH + "/" + mailbox.mId); |
| resolver.query(refreshUri, null, null, null, null); |
| } |
| } |
| } |
| } |
| } |
| } |
| // Return success |
| final Bundle response = new Bundle(2); |
| |
| response.putString(setVisibilityKey, |
| UIProvider.ConversationCursorCommand.COMMAND_RESPONSE_OK); |
| |
| final String rawFoldersKey = |
| UIProvider.ConversationCursorCommand.COMMAND_GET_RAW_FOLDERS; |
| if (params.containsKey(rawFoldersKey)) { |
| response.putParcelable(rawFoldersKey, mFolderList); |
| } |
| |
| final String convInfoKey = |
| UIProvider.ConversationCursorCommand.COMMAND_GET_CONVERSATION_INFO; |
| if (params.containsKey(convInfoKey)) { |
| response.putParcelable(convInfoKey, generateConversationInfo()); |
| } |
| |
| return response; |
| } |
| |
| private ConversationInfo generateConversationInfo() { |
| final int numMessages = getInt(getColumnIndex(ConversationColumns.NUM_MESSAGES)); |
| final ConversationInfo conversationInfo = new ConversationInfo(numMessages); |
| |
| conversationInfo.firstSnippet = getString(getColumnIndex(ConversationColumns.SNIPPET)); |
| conversationInfo.lastSnippet = conversationInfo.firstSnippet; |
| conversationInfo.firstUnreadSnippet = conversationInfo.firstSnippet; |
| |
| final boolean isRead = getInt(getColumnIndex(ConversationColumns.READ)) != 0; |
| final String senderString = getString(getColumnIndex(EmailContent.MessageColumns.DISPLAY_NAME)); |
| |
| final String fromString = getString(getColumnIndex(EmailContent.MessageColumns.FROM_LIST)); |
| final String senderEmail; |
| |
| if (fromString != null) { |
| final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(fromString); |
| if (tokens.length > 0) { |
| senderEmail = tokens[0].getAddress(); |
| } else { |
| LogUtils.d(LogUtils.TAG, "Couldn't parse sender email address"); |
| senderEmail = fromString; |
| } |
| } else { |
| senderEmail = null; |
| } |
| |
| // we *intentionally* report no participants for Draft emails so that the UI always |
| // displays the single word "Draft" as per b/13304929 |
| if (mMailboxTypeId == Mailbox.TYPE_DRAFTS) { |
| // the UI displays "Draft" in the conversation list based on this count |
| conversationInfo.draftCount = 1; |
| } else if (mMailboxTypeId == Mailbox.TYPE_SENT || |
| mMailboxTypeId == Mailbox.TYPE_OUTBOX) { |
| // for conversations in outgoing mail mailboxes return a list of recipients |
| final String recipientsString = getString(getColumnIndex( |
| EmailContent.MessageColumns.TO_LIST)); |
| final Address[] recipientAddresses = Address.parse(recipientsString); |
| for (Address recipientAddress : recipientAddresses) { |
| final String name = recipientAddress.getSimplifiedName(); |
| final String email = recipientAddress.getAddress(); |
| |
| // all recipients are said to have read all messages in the conversation |
| conversationInfo.addParticipant(new ParticipantInfo(name, email, 0, isRead)); |
| } |
| } else { |
| // for conversations in incoming mail mailboxes return the sender |
| conversationInfo.addParticipant(new ParticipantInfo(senderString, senderEmail, 0, |
| isRead)); |
| } |
| |
| return conversationInfo; |
| } |
| |
| @Override |
| public void markContentsSeen() { |
| final ContentResolver resolver = mContext.getContentResolver(); |
| final ContentValues contentValues = new ContentValues(1); |
| contentValues.put(EmailContent.MessageColumns.FLAG_SEEN, true); |
| final Uri uri = EmailContent.Message.CONTENT_URI; |
| final String where = EmailContent.MessageColumns.MAILBOX_KEY + " = ? AND " + |
| EmailContent.MessageColumns.FLAG_SEEN + " != ?"; |
| final String[] selectionArgs = {String.valueOf(mMailboxId), "1"}; |
| resolver.update(uri, contentValues, where, selectionArgs); |
| } |
| |
| @Override |
| public void emptyFolder() { |
| final ContentResolver resolver = mContext.getContentResolver(); |
| final Uri purgeUri = EmailProvider.uiUri("uipurgefolder", mMailboxId); |
| resolver.delete(purgeUri, null, null); |
| } |
| } |