Home | History | Annotate | Download | only in provider
      1 /*
      2  * Copyright (C) 2014 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 
     17 package com.android.email.provider;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.database.Cursor;
     23 import android.database.CursorWrapper;
     24 import android.net.Uri;
     25 import android.os.Bundle;
     26 import android.text.TextUtils;
     27 import android.text.format.DateUtils;
     28 import android.text.util.Rfc822Token;
     29 import android.text.util.Rfc822Tokenizer;
     30 
     31 import com.android.emailcommon.Logging;
     32 import com.android.emailcommon.mail.Address;
     33 import com.android.emailcommon.provider.EmailContent;
     34 import com.android.emailcommon.provider.Mailbox;
     35 import com.android.mail.browse.ConversationCursorOperationListener;
     36 import com.android.mail.providers.ConversationInfo;
     37 import com.android.mail.providers.Folder;
     38 import com.android.mail.providers.FolderList;
     39 import com.android.mail.providers.ParticipantInfo;
     40 import com.android.mail.providers.UIProvider;
     41 import com.android.mail.providers.UIProvider.ConversationColumns;
     42 import com.android.mail.utils.LogUtils;
     43 import com.google.common.collect.Lists;
     44 
     45 /**
     46  * Wrapper that handles the visibility feature (i.e. the conversation list is visible, so
     47  * any pending notifications for the corresponding mailbox should be canceled). We also handle
     48  * getExtras() to provide a snapshot of the mailbox's status
     49  */
     50 public class EmailConversationCursor extends CursorWrapper implements
     51         ConversationCursorOperationListener {
     52     private final long mMailboxId;
     53     private final int mMailboxTypeId;
     54     private final Context mContext;
     55     private final FolderList mFolderList;
     56     private final Bundle mExtras = new Bundle();
     57 
     58     /**
     59      * When showing a folder, if it's been at least this long since the last sync,
     60      * force a folder refresh.
     61      */
     62     private static final long AUTO_REFRESH_INTERVAL_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
     63 
     64     public EmailConversationCursor(final Context context, final Cursor cursor,
     65             final Folder folder, final long mailboxId) {
     66         super(cursor);
     67         mMailboxId = mailboxId;
     68         mContext = context;
     69         mFolderList = FolderList.copyOf(Lists.newArrayList(folder));
     70         Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
     71 
     72         if (mailbox != null) {
     73             mMailboxTypeId = mailbox.mType;
     74 
     75             mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_TOTAL_COUNT, mailbox.mTotalCount);
     76             if (mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_BACKGROUND
     77                     || mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_USER
     78                     || mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_LIVE
     79                     || mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_INITIAL_SYNC_NEEDED) {
     80                 mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS,
     81                         UIProvider.CursorStatus.LOADING);
     82             } else if (mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_NONE) {
     83                 if (mailbox.mSyncInterval == 0
     84                         && (Mailbox.isSyncableType(mailbox.mType)
     85                         || mailbox.mType == Mailbox.TYPE_SEARCH)
     86                         && !TextUtils.isEmpty(mailbox.mServerId) &&
     87                         // TODO: There's potentially a race condition here.
     88                         // Consider merging this check with the auto-sync code in respond.
     89                         System.currentTimeMillis() - mailbox.mSyncTime
     90                                 > AUTO_REFRESH_INTERVAL_MS) {
     91                     // This will be syncing momentarily
     92                     mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS,
     93                             UIProvider.CursorStatus.LOADING);
     94                 } else {
     95                     mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS,
     96                             UIProvider.CursorStatus.COMPLETE);
     97                 }
     98             } else {
     99                 LogUtils.d(Logging.LOG_TAG,
    100                         "Unknown mailbox sync status" + mailbox.mUiSyncStatus);
    101                 mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS,
    102                         UIProvider.CursorStatus.COMPLETE);
    103             }
    104         } else {
    105             mMailboxTypeId = -1;
    106             // TODO for virtual mailboxes, we may want to do something besides just fake it
    107             mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_TOTAL_COUNT,
    108                     cursor != null ? cursor.getCount() : 0);
    109             mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS,
    110                     UIProvider.CursorStatus.COMPLETE);
    111         }
    112     }
    113 
    114     @Override
    115     public Bundle getExtras() {
    116         return mExtras;
    117     }
    118 
    119     @Override
    120     public Bundle respond(Bundle params) {
    121         final String setVisibilityKey =
    122                 UIProvider.ConversationCursorCommand.COMMAND_KEY_SET_VISIBILITY;
    123         if (params.containsKey(setVisibilityKey)) {
    124             final boolean visible = params.getBoolean(setVisibilityKey);
    125             if (visible) {
    126                 // Mark all messages as seen
    127                 markContentsSeen();
    128                 if (params.containsKey(
    129                         UIProvider.ConversationCursorCommand.COMMAND_KEY_ENTERED_FOLDER)) {
    130                     Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mMailboxId);
    131                     if (mailbox != null) {
    132                         // For non-push mailboxes, if it's stale (i.e. last sync was a while
    133                         // ago), force a sync.
    134                         // TODO: Fix the check for whether we're non-push? Right now it checks
    135                         // whether we are participating in account sync rules.
    136                         if (mailbox.mSyncInterval == 0) {
    137                             final long timeSinceLastSync =
    138                                     System.currentTimeMillis() - mailbox.mSyncTime;
    139                             if (timeSinceLastSync > AUTO_REFRESH_INTERVAL_MS) {
    140                                 final ContentResolver resolver = mContext.getContentResolver();
    141                                 final Uri refreshUri = Uri.parse(EmailContent.CONTENT_URI +
    142                                         "/" + EmailProvider.QUERY_UIREFRESH + "/" + mailbox.mId);
    143                                 resolver.query(refreshUri, null, null, null, null);
    144                             }
    145                         }
    146                     }
    147                 }
    148             }
    149         }
    150         // Return success
    151         final Bundle response = new Bundle(2);
    152 
    153         response.putString(setVisibilityKey,
    154                 UIProvider.ConversationCursorCommand.COMMAND_RESPONSE_OK);
    155 
    156         final String rawFoldersKey =
    157                 UIProvider.ConversationCursorCommand.COMMAND_GET_RAW_FOLDERS;
    158         if (params.containsKey(rawFoldersKey)) {
    159             response.putParcelable(rawFoldersKey, mFolderList);
    160         }
    161 
    162         final String convInfoKey =
    163                 UIProvider.ConversationCursorCommand.COMMAND_GET_CONVERSATION_INFO;
    164         if (params.containsKey(convInfoKey)) {
    165             response.putParcelable(convInfoKey, generateConversationInfo());
    166         }
    167 
    168         return response;
    169     }
    170 
    171     private ConversationInfo generateConversationInfo() {
    172         final int numMessages = getInt(getColumnIndex(ConversationColumns.NUM_MESSAGES));
    173         final ConversationInfo conversationInfo = new ConversationInfo(numMessages);
    174 
    175         conversationInfo.firstSnippet = getString(getColumnIndex(ConversationColumns.SNIPPET));
    176         conversationInfo.lastSnippet = conversationInfo.firstSnippet;
    177         conversationInfo.firstUnreadSnippet = conversationInfo.firstSnippet;
    178 
    179         final boolean isRead = getInt(getColumnIndex(ConversationColumns.READ)) != 0;
    180         final String senderString = getString(getColumnIndex(EmailContent.MessageColumns.DISPLAY_NAME));
    181 
    182         final String fromString = getString(getColumnIndex(EmailContent.MessageColumns.FROM_LIST));
    183         final String senderEmail;
    184 
    185         if (fromString != null) {
    186             final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(fromString);
    187             if (tokens.length > 0) {
    188                 senderEmail = tokens[0].getAddress();
    189             } else {
    190                 LogUtils.d(LogUtils.TAG, "Couldn't parse sender email address");
    191                 senderEmail = fromString;
    192             }
    193         } else {
    194             senderEmail = null;
    195         }
    196 
    197         // we *intentionally* report no participants for Draft emails so that the UI always
    198         // displays the single word "Draft" as per b/13304929
    199         if (mMailboxTypeId == Mailbox.TYPE_DRAFTS) {
    200             // the UI displays "Draft" in the conversation list based on this count
    201             conversationInfo.draftCount = 1;
    202         } else if (mMailboxTypeId == Mailbox.TYPE_SENT ||
    203                 mMailboxTypeId == Mailbox.TYPE_OUTBOX) {
    204             // for conversations in outgoing mail mailboxes return a list of recipients
    205             final String recipientsString = getString(getColumnIndex(
    206                     EmailContent.MessageColumns.TO_LIST));
    207             final Address[] recipientAddresses = Address.parse(recipientsString);
    208             for (Address recipientAddress : recipientAddresses) {
    209                 final String name = recipientAddress.getSimplifiedName();
    210                 final String email = recipientAddress.getAddress();
    211 
    212                 // all recipients are said to have read all messages in the conversation
    213                 conversationInfo.addParticipant(new ParticipantInfo(name, email, 0, isRead));
    214             }
    215         } else {
    216             // for conversations in incoming mail mailboxes return the sender
    217             conversationInfo.addParticipant(new ParticipantInfo(senderString, senderEmail, 0,
    218                     isRead));
    219         }
    220 
    221         return conversationInfo;
    222     }
    223 
    224     @Override
    225     public void markContentsSeen() {
    226         final ContentResolver resolver = mContext.getContentResolver();
    227         final ContentValues contentValues = new ContentValues(1);
    228         contentValues.put(EmailContent.MessageColumns.FLAG_SEEN, true);
    229         final Uri uri = EmailContent.Message.CONTENT_URI;
    230         final String where = EmailContent.MessageColumns.MAILBOX_KEY + " = ? AND " +
    231                 EmailContent.MessageColumns.FLAG_SEEN + " != ?";
    232         final String[] selectionArgs = {String.valueOf(mMailboxId), "1"};
    233         resolver.update(uri, contentValues, where, selectionArgs);
    234     }
    235 
    236     @Override
    237     public void emptyFolder() {
    238         final ContentResolver resolver = mContext.getContentResolver();
    239         final Uri purgeUri = EmailProvider.uiUri("uipurgefolder", mMailboxId);
    240         resolver.delete(purgeUri, null, null);
    241     }
    242 }
    243