1 /* 2 * Copyright (C) 2011 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.activity; 18 19 import android.content.ContentUris; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.database.Cursor; 23 24 import com.android.email.Clock; 25 import com.android.email.Controller; 26 import com.android.emailcommon.provider.EmailContent; 27 import com.android.emailcommon.provider.EmailContent.MailboxColumns; 28 import com.android.emailcommon.provider.Mailbox; 29 import com.android.emailcommon.utility.EmailAsyncTask; 30 import com.google.common.annotations.VisibleForTesting; 31 import com.google.common.collect.Maps; 32 33 import java.util.ArrayList; 34 import java.util.HashMap; 35 36 /** 37 * Manages recent data for mailboxes. 38 */ 39 public class RecentMailboxManager { 40 @VisibleForTesting 41 static Clock sClock = Clock.INSTANCE; 42 @VisibleForTesting 43 static RecentMailboxManager sInstance; 44 45 public static String RECENT_MAILBOXES_SORT_ORDER = MailboxColumns.DISPLAY_NAME; 46 47 /** The maximum number of results to retrieve */ 48 private static final int LIMIT_RESULTS = 5; 49 /** Query to find the top most recent mailboxes */ 50 private static final String RECENT_SELECTION = 51 MailboxColumns.ID + " IN " + 52 "( SELECT " + MailboxColumns.ID 53 + " FROM " + Mailbox.TABLE_NAME 54 + " WHERE ( " + MailboxColumns.ACCOUNT_KEY + "=? " 55 + " AND " + Mailbox.USER_VISIBLE_MAILBOX_SELECTION 56 + " AND " + MailboxColumns.TYPE + "!=" + Mailbox.TYPE_INBOX 57 + " AND " + MailboxColumns.LAST_TOUCHED_TIME + ">0 )" 58 + " ORDER BY " + MailboxColumns.LAST_TOUCHED_TIME + " DESC" 59 + " LIMIT ? )"; 60 /** Similar query to {@link #RECENT_SELECTION}, except, exclude all but user mailboxes */ 61 private static final String RECENT_SELECTION_WITH_EXCLUSIONS = 62 MailboxColumns.ID + " IN " + 63 "( SELECT " + MailboxColumns.ID 64 + " FROM " + Mailbox.TABLE_NAME 65 + " WHERE ( " + MailboxColumns.ACCOUNT_KEY + "=? " 66 + " AND " + Mailbox.USER_VISIBLE_MAILBOX_SELECTION 67 + " AND " + MailboxColumns.TYPE + "=" + Mailbox.TYPE_MAIL 68 + " AND " + MailboxColumns.LAST_TOUCHED_TIME + ">0 )" 69 + " ORDER BY " + MailboxColumns.LAST_TOUCHED_TIME + " DESC" 70 + " LIMIT ? )"; 71 72 /** Mailbox types for default "recent mailbox" entries if none exist */ 73 @VisibleForTesting 74 static final int[] DEFAULT_RECENT_TYPES = new int[] { 75 Mailbox.TYPE_DRAFTS, 76 Mailbox.TYPE_SENT, 77 }; 78 79 private final Context mContext; 80 private final HashMap<Long, Boolean> mDefaultRecentsInitialized; 81 82 public static synchronized RecentMailboxManager getInstance(Context context) { 83 if (sInstance == null) { 84 sInstance = new RecentMailboxManager(context); 85 } 86 return sInstance; 87 } 88 89 /** Hide constructor */ 90 private RecentMailboxManager(Context context) { 91 mContext = context; 92 mDefaultRecentsInitialized = Maps.newHashMap(); 93 } 94 95 /** Updates the specified mailbox's touch time. Returns an async task for test only. */ 96 public EmailAsyncTask<Void, Void, Void> touch(long accountId, long mailboxId) { 97 return fireAndForget(accountId, mailboxId, sClock.getTime()); 98 } 99 100 /** 101 * Gets the most recently touched mailboxes for the specified account. If there are no 102 * recent mailboxes and withExclusions is {@code false}, default recent mailboxes will 103 * be returned. 104 * <p><em>WARNING</em>: This method blocks on the database. 105 * @param accountId The ID of the account to load the recent list. 106 * @param withExclusions If {@code false}, all mailboxes are eligible for the recent list. 107 * Otherwise, only user defined mailboxes are eligible for the recent list. 108 */ 109 public ArrayList<Long> getMostRecent(long accountId, boolean withExclusions) { 110 ensureDefaultsInitialized(accountId, sClock.getTime()); 111 112 String selection = withExclusions ? RECENT_SELECTION_WITH_EXCLUSIONS : RECENT_SELECTION; 113 ArrayList<Long> returnList = new ArrayList<Long>(); 114 Cursor cursor = mContext.getContentResolver().query(Mailbox.CONTENT_URI, 115 EmailContent.ID_PROJECTION, 116 selection, 117 new String[] { Long.toString(accountId), Integer.toString(LIMIT_RESULTS) }, 118 RECENT_MAILBOXES_SORT_ORDER); 119 try { 120 while (cursor.moveToNext()) { 121 returnList.add(cursor.getLong(EmailContent.ID_PROJECTION_COLUMN)); 122 } 123 } finally { 124 cursor.close(); 125 } 126 return returnList; 127 } 128 129 /** Updates the last touched time for the mailbox in the background */ 130 private EmailAsyncTask<Void, Void, Void> fireAndForget( 131 final long accountId, final long mailboxId, final long time) { 132 return EmailAsyncTask.runAsyncParallel(new Runnable() { 133 @Override 134 public void run() { 135 ensureDefaultsInitialized(accountId, time); 136 touchMailboxSynchronous(accountId, mailboxId, time); 137 } 138 }); 139 } 140 141 private void touchMailboxSynchronous(long accountId, long mailboxId, long time) { 142 ContentValues values = new ContentValues(); 143 values.put(MailboxColumns.LAST_TOUCHED_TIME, time); 144 mContext.getContentResolver().update( 145 ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), 146 values, null, null); 147 } 148 149 /** 150 * Ensures the default recent mailboxes have been set for this account. 151 */ 152 private synchronized void ensureDefaultsInitialized(long accountId, long time) { 153 if (Boolean.TRUE.equals(mDefaultRecentsInitialized.get(accountId))) { 154 return; 155 } 156 157 String[] args = new String[] { Long.toString(accountId), Integer.toString(LIMIT_RESULTS) }; 158 if (EmailContent.count(mContext, Mailbox.CONTENT_URI, RECENT_SELECTION, args) == 0) { 159 // There are no recent mailboxes at all. Populate with default set. 160 for (int type : DEFAULT_RECENT_TYPES) { 161 long mailbox = Controller.getInstance(mContext).findOrCreateMailboxOfType( 162 accountId, type); 163 touchMailboxSynchronous(accountId, mailbox, time); 164 } 165 } 166 167 mDefaultRecentsInitialized.put(accountId, true); 168 } 169 } 170