blob: c49a0f3bc893b41c99b01ac0ca9947f066c9679b [file] [log] [blame]
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -08001/**
2 * Copyright (c) 2011, Google Inc.
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
17package com.android.mail.ui;
18
Vikram Aggarwal27e85f22012-03-05 16:29:17 -080019import android.content.ContentValues;
20import android.content.Context;
21import android.database.Cursor;
Marc Blankbc748ac2012-03-21 10:08:53 -070022import android.net.Uri;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -080023import android.os.AsyncTask;
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -080024
25import com.android.mail.providers.Account;
Vikram Aggarwal7c401b72012-08-13 16:43:47 -070026import com.android.mail.providers.AccountObserver;
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -080027import com.android.mail.providers.Folder;
Marc Blank167faa82012-03-21 13:11:53 -070028import com.android.mail.providers.Settings;
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -080029import com.android.mail.utils.LogUtils;
30import com.android.mail.utils.LruCache;
Marc Blank10ddc192012-04-16 09:47:51 -070031import com.android.mail.utils.Utils;
Paul Westbrook5a64acd2012-09-07 10:30:04 -070032import com.google.common.collect.Lists;
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -080033
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -080034import java.util.ArrayList;
Marc Blank3232a962012-03-08 15:32:37 -080035import java.util.Collections;
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -080036import java.util.Comparator;
Marc Blank3232a962012-03-08 15:32:37 -080037import java.util.List;
Paul Westbrook5a64acd2012-09-07 10:30:04 -070038import java.util.concurrent.atomic.AtomicInteger;
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -080039
40/**
41 * A self-updating list of folder canonical names for the N most recently touched folders, ordered
42 * from least-recently-touched to most-recently-touched. N is a fixed size determined upon
43 * creation.
44 *
45 * RecentFoldersCache returns lists of this type, and will keep them updated when observers are
46 * registered on them.
47 *
48 */
49public final class RecentFolderList {
Marc Blankbc748ac2012-03-21 10:08:53 -070050 private static final String TAG = "RecentFolderList";
Vikram Aggarwal27e85f22012-03-05 16:29:17 -080051 /** The application context */
52 private final Context mContext;
53 /** The current account */
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -080054 private Account mAccount = null;
Marc Blankbc748ac2012-03-21 10:08:53 -070055
Vikram Aggarwal2c0032d2012-09-04 10:30:07 -070056 /** The actual cache: map of folder URIs to folder objects. */
Paul Westbrook5a64acd2012-09-07 10:30:04 -070057 private final LruCache<String, RecentFolderListEntry> mFolderCache;
Marc Blankbc748ac2012-03-21 10:08:53 -070058 /**
Marc Blank167faa82012-03-21 13:11:53 -070059 * We want to show at most five recent folders
Marc Blankbc748ac2012-03-21 10:08:53 -070060 */
Marc Blank167faa82012-03-21 13:11:53 -070061 private final static int MAX_RECENT_FOLDERS = 5;
62 /**
63 * We exclude the default inbox for the account and the current folder; these might be the
64 * same, but we'll allow for both
65 */
66 private final static int MAX_EXCLUDED_FOLDERS = 2;
Marc Blankbc748ac2012-03-21 10:08:53 -070067
Vikram Aggarwal7c401b72012-08-13 16:43:47 -070068 private final AccountObserver mAccountObserver = new AccountObserver() {
69 @Override
70 public void onChanged(Account newAccount) {
71 setCurrentAccount(newAccount);
72 }
73 };
74
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -080075 /**
76 * Compare based on alphanumeric name of the folder, ignoring case.
77 */
78 private static final Comparator<Folder> ALPHABET_IGNORECASE = new Comparator<Folder>() {
79 @Override
80 public int compare(Folder lhs, Folder rhs) {
81 return lhs.name.compareToIgnoreCase(rhs.name);
82 }
83 };
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -080084 /**
85 * Class to store the recent folder list asynchronously.
86 */
Marc Blankbc748ac2012-03-21 10:08:53 -070087 private class StoreRecent extends AsyncTask<Void, Void, Void> {
Vikram Aggarwal7c401b72012-08-13 16:43:47 -070088 /**
89 * Copy {@link RecentFolderList#mAccount} in case the account changes between when the
90 * AsyncTask is created and when it is executed.
91 */
92 @SuppressWarnings("hiding")
93 private final Account mAccount;
94 private final Folder mFolder;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -080095
Vikram Aggarwal792ccba2012-03-27 13:46:57 -070096 /**
97 * Create a new asynchronous task to store the recent folder list. Both the account
98 * and the folder should be non-null.
99 * @param account
100 * @param folder
101 */
Marc Blankbc748ac2012-03-21 10:08:53 -0700102 public StoreRecent(Account account, Folder folder) {
Vikram Aggarwal792ccba2012-03-27 13:46:57 -0700103 assert (account != null && folder != null);
Marc Blankc972b182012-03-12 18:03:43 -0700104 mAccount = account;
Marc Blankbc748ac2012-03-21 10:08:53 -0700105 mFolder = folder;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800106 }
107
108 @Override
Marc Blankbc748ac2012-03-21 10:08:53 -0700109 protected Void doInBackground(Void... v) {
Vikram Aggarwal677cbef2012-06-11 15:38:56 -0700110 final Uri uri = mAccount.recentFolderListUri;
Marc Blank10ddc192012-04-16 09:47:51 -0700111 if (!Utils.isEmpty(uri)) {
Marc Blankbc748ac2012-03-21 10:08:53 -0700112 ContentValues values = new ContentValues();
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -0700113 // Only the folder URIs are provided. Providers are free to update their specific
114 // information, though most will probably write the current timestamp.
115 values.put(mFolder.uri.toString(), 0);
Paul Westbrookad6a2752012-04-04 16:58:13 -0700116 LogUtils.i(TAG, "Save: %s", mFolder.name);
Marc Blankbc748ac2012-03-21 10:08:53 -0700117 mContext.getContentResolver().update(uri, values, null, null);
118 }
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800119 return null;
120 }
121 }
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -0800122
123 /**
124 * Create a Recent Folder List from the given account. This will query the UIProvider to
125 * retrieve the RecentFolderList from persistent storage (if any).
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700126 * @param context
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -0800127 */
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700128 public RecentFolderList(Context context) {
Paul Westbrook5a64acd2012-09-07 10:30:04 -0700129 mFolderCache = new LruCache<String, RecentFolderListEntry>(
130 MAX_RECENT_FOLDERS + MAX_EXCLUDED_FOLDERS);
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800131 mContext = context;
Vikram Aggarwal27e85f22012-03-05 16:29:17 -0800132 }
133
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800134 /**
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700135 * Initialize the {@link RecentFolderList} with a controllable activity.
136 * @param activity
137 */
138 public void initialize(ControllableActivity activity){
139 setCurrentAccount(mAccountObserver.initialize(activity.getAccountController()));
140 }
141
142 /**
Marc Blank167faa82012-03-21 13:11:53 -0700143 * Change the current account. When a cursor over the recent folders for this account is
144 * available, the client <b>must</b> call {@link #loadFromUiProvider(Cursor)} with the updated
145 * cursor. Till then, the recent account list will be empty.
146 * @param account the new current account
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800147 */
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700148 private void setCurrentAccount(Account account) {
Vikram Aggarwal27e85f22012-03-05 16:29:17 -0800149 mAccount = account;
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800150 mFolderCache.clear();
Vikram Aggarwal27e85f22012-03-05 16:29:17 -0800151 }
152
153 /**
Vikram Aggarwalfa4b47e2012-03-09 13:02:46 -0800154 * Load the account information from the UI provider given the cursor over the recent folders.
Marc Blank167faa82012-03-21 13:11:53 -0700155 * @param c a cursor over the recent folders.
Vikram Aggarwal27e85f22012-03-05 16:29:17 -0800156 */
Marc Blank167faa82012-03-21 13:11:53 -0700157 public void loadFromUiProvider(Cursor c) {
158 if (mAccount == null || c == null) {
Vikram Aggarwal27e85f22012-03-05 16:29:17 -0800159 return;
Marc Blankbc748ac2012-03-21 10:08:53 -0700160 }
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -0700161 LogUtils.d(TAG, "Number of recents = %d", c.getCount());
Vikram Aggarwal27e85f22012-03-05 16:29:17 -0800162 int i = 0;
Paul Westbrook317348a2012-08-24 17:35:01 -0700163 if (!c.moveToLast()) {
164 LogUtils.d(TAG, "Not able to move to last in recent labels cursor");
165 return;
166 }
Vikram Aggarwalf991bee2012-08-24 09:11:52 -0700167 // Add them backwards, since the most recent values are at the beginning in the cursor.
168 // This enables older values to fall off the LRU cache. Also, read all values, just in case
169 // there are duplicates in the cursor.
170 do {
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -0700171 final Folder folder = new Folder(c);
Paul Westbrook5a64acd2012-09-07 10:30:04 -0700172 final RecentFolderListEntry entry = new RecentFolderListEntry(folder);
173 mFolderCache.putElement(folder.uri.toString(), entry);
Vikram Aggarwal27d89ad2012-06-12 13:38:40 -0700174 LogUtils.v(TAG, "Account %s, Recent: %s", mAccount.name, folder.name);
Vikram Aggarwalf991bee2012-08-24 09:11:52 -0700175 } while (c.moveToPrevious());
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -0800176 }
177
178 /**
Marc Blankbc748ac2012-03-21 10:08:53 -0700179 * Marks the given folder as 'accessed' by the user interface, its entry is updated in the
Vikram Aggarwal792ccba2012-03-27 13:46:57 -0700180 * recent folder list, and the current time is written to the provider. This should never
181 * be called with a null folder.
Marc Blank167faa82012-03-21 13:11:53 -0700182 * @param folder the folder we touched
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -0800183 */
Marc Blank2675dbc2012-04-03 10:17:13 -0700184 public void touchFolder(Folder folder, Account account) {
Vikram Aggarwal792ccba2012-03-27 13:46:57 -0700185 // We haven't got a valid account yet, cannot proceed.
Marc Blank2675dbc2012-04-03 10:17:13 -0700186 if (mAccount == null || !mAccount.equals(account)) {
187 if (account != null) {
188 setCurrentAccount(account);
189 } else {
190 LogUtils.w(TAG, "No account set for setting recent folders?");
191 return;
192 }
Vikram Aggarwal792ccba2012-03-27 13:46:57 -0700193 }
194 assert (folder != null);
Paul Westbrook5a64acd2012-09-07 10:30:04 -0700195 final RecentFolderListEntry entry = new RecentFolderListEntry(folder);
196 mFolderCache.putElement(folder.uri.toString(), entry);
Marc Blankbc748ac2012-03-21 10:08:53 -0700197 new StoreRecent(mAccount, folder).execute();
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -0800198 }
199
200 /**
Marc Blank167faa82012-03-21 13:11:53 -0700201 * Generate a sorted list of recent folders, excluding the passed in folder (if any) and
Vikram Aggarwalf991bee2012-08-24 09:11:52 -0700202 * default inbox for the current account. This must be called <em>after</em>
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700203 * {@link #setCurrentAccount(Account)} has been called.
Vikram Aggarwalf991bee2012-08-24 09:11:52 -0700204 * Returns a list of size {@value #MAX_RECENT_FOLDERS} or smaller.
Vikram Aggarwal58cad2e2012-08-28 16:18:23 -0700205 * @param excludedFolderUri the uri of folder to be excluded (typically the current folder)
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -0800206 */
Vikram Aggarwal58cad2e2012-08-28 16:18:23 -0700207 public ArrayList<Folder> getRecentFolderList(Uri excludedFolderUri) {
Vikram Aggarwal025eba82012-05-08 10:45:30 -0700208 final ArrayList<Uri> excludedUris = new ArrayList<Uri>();
Vikram Aggarwal58cad2e2012-08-28 16:18:23 -0700209 if (excludedFolderUri != null) {
210 excludedUris.add(excludedFolderUri);
Marc Blank167faa82012-03-21 13:11:53 -0700211 }
Vikram Aggarwal9da85df2012-05-09 15:34:23 -0700212 final Uri defaultInbox = (mAccount == null) ?
Vikram Aggarwalf991bee2012-08-24 09:11:52 -0700213 Uri.EMPTY : Settings.getDefaultInboxUri(mAccount.settings);
Vikram Aggarwal1e57e672012-05-07 14:48:24 -0700214 if (!defaultInbox.equals(Uri.EMPTY)) {
Vikram Aggarwal1e57e672012-05-07 14:48:24 -0700215 excludedUris.add(defaultInbox);
Marc Blank167faa82012-03-21 13:11:53 -0700216 }
Paul Westbrook5a64acd2012-09-07 10:30:04 -0700217 final List<RecentFolderListEntry> recent = Lists.newArrayList();
218 recent.addAll(mFolderCache.values());
219 Collections.sort(recent);
220
221 final ArrayList<Folder> recentFolders = Lists.newArrayList();
222 for (final RecentFolderListEntry entry : recent) {
223 if (!excludedUris.contains(entry.mFolder.uri)) {
224 recentFolders.add(entry.mFolder);
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -0800225 }
Vikram Aggarwalf991bee2012-08-24 09:11:52 -0700226 if (recentFolders.size() == MAX_RECENT_FOLDERS) {
227 break;
228 }
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -0800229 }
Paul Westbrook5a64acd2012-09-07 10:30:04 -0700230
Vikram Aggarwalf991bee2012-08-24 09:11:52 -0700231 // Sort the values as the very last step.
232 Collections.sort(recentFolders, ALPHABET_IGNORECASE);
Paul Westbrook5a64acd2012-09-07 10:30:04 -0700233
Marc Blank167faa82012-03-21 13:11:53 -0700234 return recentFolders;
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -0800235 }
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700236
237 /**
238 * Destroys this instance. The object is unusable after this has been called.
239 */
240 public void destroy() {
241 mAccountObserver.unregisterAndDestroy();
242 }
Paul Westbrook5a64acd2012-09-07 10:30:04 -0700243
244 private static class RecentFolderListEntry implements Comparable<RecentFolderListEntry> {
245 private static final AtomicInteger SEQUENCE_GENERATOR = new AtomicInteger();
246
247 private final Folder mFolder;
248 private final int mSequence;
249
250 RecentFolderListEntry(Folder folder) {
251 mFolder = folder;
252 mSequence = SEQUENCE_GENERATOR.getAndIncrement();
253 }
254
255 /**
256 * Ensure that RecentFolderListEntry objects with greater sequence number will appear
257 * before objects with lower sequence numbers
258 */
259 @Override
260 public int compareTo(RecentFolderListEntry t) {
261 return t.mSequence - mSequence;
262 }
263 }
Vikram Aggarwal1a4bcc02012-03-01 10:09:44 -0800264}