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_ERROR,
     76                     mailbox.mUiLastSyncResult);
     77             mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_TOTAL_COUNT, mailbox.mTotalCount);
     78             if (mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_BACKGROUND
     79                     || mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_USER
     80                     || mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_LIVE) {
     81                 mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS,
     82                         UIProvider.CursorStatus.LOADING);
     83             } else if (mailbox.mUiSyncStatus == EmailContent.SYNC_STATUS_NONE) {
     84                 if (mailbox.mSyncInterval == 0
     85                         && (Mailbox.isSyncableType(mailbox.mType)
     86                         || mailbox.mType == Mailbox.TYPE_SEARCH)
     87                         && !TextUtils.isEmpty(mailbox.mServerId) &&
     88                         // TODO: There's potentially a race condition here.
     89                         // Consider merging this check with the auto-sync code in respond.
     90                         System.currentTimeMillis() - mailbox.mSyncTime
     91                                 > AUTO_REFRESH_INTERVAL_MS) {
     92                     // This will be syncing momentarily
     93                     mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS,
     94                             UIProvider.CursorStatus.LOADING);
     95                 } else {
     96                     mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS,
     97                             UIProvider.CursorStatus.COMPLETE);
     98                 }
     99             } else {
    100                 LogUtils.d(Logging.LOG_TAG,
    101                         "Unknown mailbox sync status" + mailbox.mUiSyncStatus);
    102                 mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS,
    103                         UIProvider.CursorStatus.COMPLETE);
    104             }
    105         } else {
    106             mMailboxTypeId = -1;
    107             // TODO for virtual mailboxes, we may want to do something besides just fake it
    108             mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_ERROR,
    109                     UIProvider.LastSyncResult.SUCCESS);
    110             mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_TOTAL_COUNT,
    111                     cursor != null ? cursor.getCount() : 0);
    112             mExtras.putInt(UIProvider.CursorExtraKeys.EXTRA_STATUS,
    113                     UIProvider.CursorStatus.COMPLETE);
    114         }
    115     }
    116 
    117     @Override
    118     public Bundle getExtras() {
    119         return mExtras;
    120     }
    121 
    122     @Override
    123     public Bundle respond(Bundle params) {
    124         final String setVisibilityKey =
    125                 UIProvider.ConversationCursorCommand.COMMAND_KEY_SET_VISIBILITY;
    126         if (params.containsKey(setVisibilityKey)) {
    127             final boolean visible = params.getBoolean(setVisibilityKey);
    128             if (visible) {
    129                 // Mark all messages as seen
    130                 markContentsSeen();
    131                 if (params.containsKey(
    132                         UIProvider.ConversationCursorCommand.COMMAND_KEY_ENTERED_FOLDER)) {
    133                     Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mMailboxId);
    134                     if (mailbox != null) {
    135                         // For non-push mailboxes, if it's stale (i.e. last sync was a while
    136                         // ago), force a sync.
    137                         // TODO: Fix the check for whether we're non-push? Right now it checks
    138                         // whether we are participating in account sync rules.
    139                         if (mailbox.mSyncInterval == 0) {
    140                             final long timeSinceLastSync =
    141                                     System.currentTimeMillis() - mailbox.mSyncTime;
    142                             if (timeSinceLastSync > AUTO_REFRESH_INTERVAL_MS) {
    143                                 final ContentResolver resolver = mContext.getContentResolver();
    144                                 final Uri refreshUri = Uri.parse(EmailContent.CONTENT_URI +
    145                                         "/" + EmailProvider.QUERY_UIREFRESH + "/" + mailbox.mId);
    146                                 resolver.query(refreshUri, null, null, null, null);
    147                             }
    148                         }
    149                     }
    150                 }
    151             }
    152         }
    153         // Return success
    154         final Bundle response = new Bundle(2);
    155 
    156         response.putString(setVisibilityKey,
    157                 UIProvider.ConversationCursorCommand.COMMAND_RESPONSE_OK);
    158 
    159         final String rawFoldersKey =
    160                 UIProvider.ConversationCursorCommand.COMMAND_GET_RAW_FOLDERS;
    161         if (params.containsKey(rawFoldersKey)) {
    162             response.putParcelable(rawFoldersKey, mFolderList);
    163         }
    164 
    165         final String convInfoKey =
    166                 UIProvider.ConversationCursorCommand.COMMAND_GET_CONVERSATION_INFO;
    167         if (params.containsKey(convInfoKey)) {
    168             response.putParcelable(convInfoKey, generateConversationInfo());
    169         }
    170 
    171         return response;
    172     }
    173 
    174     private ConversationInfo generateConversationInfo() {
    175         final int numMessages = getInt(getColumnIndex(ConversationColumns.NUM_MESSAGES));
    176         final ConversationInfo conversationInfo = new ConversationInfo(numMessages);
    177 
    178         conversationInfo.firstSnippet = getString(getColumnIndex(ConversationColumns.SNIPPET));
    179         conversationInfo.lastSnippet = conversationInfo.firstSnippet;
    180         conversationInfo.firstUnreadSnippet = conversationInfo.firstSnippet;
    181 
    182         final boolean isRead = getInt(getColumnIndex(ConversationColumns.READ)) != 0;
    183         final String senderString = getString(getColumnIndex(EmailContent.MessageColumns.DISPLAY_NAME));
    184 
    185         final String fromString = getString(getColumnIndex(EmailContent.MessageColumns.FROM_LIST));
    186         final String senderEmail;
    187 
    188         if (fromString != null) {
    189             final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(fromString);
    190             if (tokens.length > 0) {
    191                 senderEmail = tokens[0].getAddress();
    192             } else {
    193                 LogUtils.d(LogUtils.TAG, "Couldn't parse sender email address");
    194                 senderEmail = fromString;
    195             }
    196         } else {
    197             senderEmail = null;
    198         }
    199 
    200         // we *intentionally* report no participants for Draft emails so that the UI always
    201         // displays the single word "Draft" as per b/13304929
    202         if (mMailboxTypeId == Mailbox.TYPE_DRAFTS) {
    203             // the UI displays "Draft" in the conversation list based on this count
    204             conversationInfo.draftCount = 1;
    205         } else if (mMailboxTypeId == Mailbox.TYPE_SENT ||
    206                 mMailboxTypeId == Mailbox.TYPE_OUTBOX) {
    207             // for conversations in outgoing mail mailboxes return a list of recipients
    208             final String recipientsString = getString(getColumnIndex(
    209                     EmailContent.MessageColumns.TO_LIST));
    210             final Address[] recipientAddresses = Address.parse(recipientsString);
    211             for (Address recipientAddress : recipientAddresses) {
    212                 final String name = recipientAddress.getSimplifiedName();
    213                 final String email = recipientAddress.getAddress();
    214 
    215                 // all recipients are said to have read all messages in the conversation
    216                 conversationInfo.addParticipant(new ParticipantInfo(name, email, 0, isRead));
    217             }
    218         } else {
    219             // for conversations in incoming mail mailboxes return the sender
    220             conversationInfo.addParticipant(new ParticipantInfo(senderString, senderEmail, 0,
    221                     isRead));
    222         }
    223 
    224         return conversationInfo;
    225     }
    226 
    227     @Override
    228     public void markContentsSeen() {
    229         final ContentResolver resolver = mContext.getContentResolver();
    230         final ContentValues contentValues = new ContentValues(1);
    231         contentValues.put(EmailContent.MessageColumns.FLAG_SEEN, true);
    232         final Uri uri = EmailContent.Message.CONTENT_URI;
    233         final String where = EmailContent.MessageColumns.MAILBOX_KEY + " = ? AND " +
    234                 EmailContent.MessageColumns.FLAG_SEEN + " != ?";
    235         final String[] selectionArgs = {String.valueOf(mMailboxId), "1"};
    236         resolver.update(uri, contentValues, where, selectionArgs);
    237     }
    238 
    239     @Override
    240     public void emptyFolder() {
    241         final ContentResolver resolver = mContext.getContentResolver();
    242         final Uri purgeUri = EmailProvider.uiUri("uipurgefolder", mMailboxId);
    243         resolver.delete(purgeUri, null, null);
    244     }
    245 }
    246