Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 1 | /** |
| 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 | |
| 17 | package com.android.mail.ui; |
| 18 | |
Vikram Aggarwal | 27e85f2 | 2012-03-05 16:29:17 -0800 | [diff] [blame] | 19 | import android.content.ContentValues; |
| 20 | import android.content.Context; |
| 21 | import android.database.Cursor; |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 22 | import android.net.Uri; |
Vikram Aggarwal | fa4b47e | 2012-03-09 13:02:46 -0800 | [diff] [blame] | 23 | import android.os.AsyncTask; |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 24 | |
| 25 | import com.android.mail.providers.Account; |
Vikram Aggarwal | 7c401b7 | 2012-08-13 16:43:47 -0700 | [diff] [blame] | 26 | import com.android.mail.providers.AccountObserver; |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 27 | import com.android.mail.providers.Folder; |
Marc Blank | 167faa8 | 2012-03-21 13:11:53 -0700 | [diff] [blame] | 28 | import com.android.mail.providers.Settings; |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 29 | import com.android.mail.utils.LogUtils; |
| 30 | import com.android.mail.utils.LruCache; |
Marc Blank | 10ddc19 | 2012-04-16 09:47:51 -0700 | [diff] [blame] | 31 | import com.android.mail.utils.Utils; |
Paul Westbrook | 5a64acd | 2012-09-07 10:30:04 -0700 | [diff] [blame] | 32 | import com.google.common.collect.Lists; |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 33 | |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 34 | import java.util.ArrayList; |
Marc Blank | 3232a96 | 2012-03-08 15:32:37 -0800 | [diff] [blame] | 35 | import java.util.Collections; |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 36 | import java.util.Comparator; |
Marc Blank | 3232a96 | 2012-03-08 15:32:37 -0800 | [diff] [blame] | 37 | import java.util.List; |
Paul Westbrook | 5a64acd | 2012-09-07 10:30:04 -0700 | [diff] [blame] | 38 | import java.util.concurrent.atomic.AtomicInteger; |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 39 | |
| 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 | */ |
| 49 | public final class RecentFolderList { |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 50 | private static final String TAG = "RecentFolderList"; |
Vikram Aggarwal | 27e85f2 | 2012-03-05 16:29:17 -0800 | [diff] [blame] | 51 | /** The application context */ |
| 52 | private final Context mContext; |
| 53 | /** The current account */ |
Vikram Aggarwal | ec5cbf7 | 2012-03-08 15:10:35 -0800 | [diff] [blame] | 54 | private Account mAccount = null; |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 55 | |
Vikram Aggarwal | 2c0032d | 2012-09-04 10:30:07 -0700 | [diff] [blame] | 56 | /** The actual cache: map of folder URIs to folder objects. */ |
Paul Westbrook | 5a64acd | 2012-09-07 10:30:04 -0700 | [diff] [blame] | 57 | private final LruCache<String, RecentFolderListEntry> mFolderCache; |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 58 | /** |
Marc Blank | 167faa8 | 2012-03-21 13:11:53 -0700 | [diff] [blame] | 59 | * We want to show at most five recent folders |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 60 | */ |
Marc Blank | 167faa8 | 2012-03-21 13:11:53 -0700 | [diff] [blame] | 61 | 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 Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 67 | |
Vikram Aggarwal | 7c401b7 | 2012-08-13 16:43:47 -0700 | [diff] [blame] | 68 | private final AccountObserver mAccountObserver = new AccountObserver() { |
| 69 | @Override |
| 70 | public void onChanged(Account newAccount) { |
| 71 | setCurrentAccount(newAccount); |
| 72 | } |
| 73 | }; |
| 74 | |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 75 | /** |
| 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 Aggarwal | fa4b47e | 2012-03-09 13:02:46 -0800 | [diff] [blame] | 84 | /** |
| 85 | * Class to store the recent folder list asynchronously. |
| 86 | */ |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 87 | private class StoreRecent extends AsyncTask<Void, Void, Void> { |
Vikram Aggarwal | 7c401b7 | 2012-08-13 16:43:47 -0700 | [diff] [blame] | 88 | /** |
| 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 Aggarwal | fa4b47e | 2012-03-09 13:02:46 -0800 | [diff] [blame] | 95 | |
Vikram Aggarwal | 792ccba | 2012-03-27 13:46:57 -0700 | [diff] [blame] | 96 | /** |
| 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 Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 102 | public StoreRecent(Account account, Folder folder) { |
Vikram Aggarwal | 792ccba | 2012-03-27 13:46:57 -0700 | [diff] [blame] | 103 | assert (account != null && folder != null); |
Marc Blank | c972b18 | 2012-03-12 18:03:43 -0700 | [diff] [blame] | 104 | mAccount = account; |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 105 | mFolder = folder; |
Vikram Aggarwal | fa4b47e | 2012-03-09 13:02:46 -0800 | [diff] [blame] | 106 | } |
| 107 | |
| 108 | @Override |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 109 | protected Void doInBackground(Void... v) { |
Vikram Aggarwal | 677cbef | 2012-06-11 15:38:56 -0700 | [diff] [blame] | 110 | final Uri uri = mAccount.recentFolderListUri; |
Marc Blank | 10ddc19 | 2012-04-16 09:47:51 -0700 | [diff] [blame] | 111 | if (!Utils.isEmpty(uri)) { |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 112 | ContentValues values = new ContentValues(); |
Vikram Aggarwal | 27d89ad | 2012-06-12 13:38:40 -0700 | [diff] [blame] | 113 | // 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 Westbrook | ad6a275 | 2012-04-04 16:58:13 -0700 | [diff] [blame] | 116 | LogUtils.i(TAG, "Save: %s", mFolder.name); |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 117 | mContext.getContentResolver().update(uri, values, null, null); |
| 118 | } |
Vikram Aggarwal | fa4b47e | 2012-03-09 13:02:46 -0800 | [diff] [blame] | 119 | return null; |
| 120 | } |
| 121 | } |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 122 | |
| 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 Aggarwal | 7c401b7 | 2012-08-13 16:43:47 -0700 | [diff] [blame] | 126 | * @param context |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 127 | */ |
Vikram Aggarwal | 025eba8 | 2012-05-08 10:45:30 -0700 | [diff] [blame] | 128 | public RecentFolderList(Context context) { |
Paul Westbrook | 5a64acd | 2012-09-07 10:30:04 -0700 | [diff] [blame] | 129 | mFolderCache = new LruCache<String, RecentFolderListEntry>( |
| 130 | MAX_RECENT_FOLDERS + MAX_EXCLUDED_FOLDERS); |
Vikram Aggarwal | fa4b47e | 2012-03-09 13:02:46 -0800 | [diff] [blame] | 131 | mContext = context; |
Vikram Aggarwal | 27e85f2 | 2012-03-05 16:29:17 -0800 | [diff] [blame] | 132 | } |
| 133 | |
Vikram Aggarwal | ec5cbf7 | 2012-03-08 15:10:35 -0800 | [diff] [blame] | 134 | /** |
Vikram Aggarwal | 7c401b7 | 2012-08-13 16:43:47 -0700 | [diff] [blame] | 135 | * 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 Blank | 167faa8 | 2012-03-21 13:11:53 -0700 | [diff] [blame] | 143 | * 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 Aggarwal | ec5cbf7 | 2012-03-08 15:10:35 -0800 | [diff] [blame] | 147 | */ |
Vikram Aggarwal | 7c401b7 | 2012-08-13 16:43:47 -0700 | [diff] [blame] | 148 | private void setCurrentAccount(Account account) { |
Vikram Aggarwal | 27e85f2 | 2012-03-05 16:29:17 -0800 | [diff] [blame] | 149 | mAccount = account; |
Vikram Aggarwal | fa4b47e | 2012-03-09 13:02:46 -0800 | [diff] [blame] | 150 | mFolderCache.clear(); |
Vikram Aggarwal | 27e85f2 | 2012-03-05 16:29:17 -0800 | [diff] [blame] | 151 | } |
| 152 | |
| 153 | /** |
Vikram Aggarwal | fa4b47e | 2012-03-09 13:02:46 -0800 | [diff] [blame] | 154 | * Load the account information from the UI provider given the cursor over the recent folders. |
Marc Blank | 167faa8 | 2012-03-21 13:11:53 -0700 | [diff] [blame] | 155 | * @param c a cursor over the recent folders. |
Vikram Aggarwal | 27e85f2 | 2012-03-05 16:29:17 -0800 | [diff] [blame] | 156 | */ |
Marc Blank | 167faa8 | 2012-03-21 13:11:53 -0700 | [diff] [blame] | 157 | public void loadFromUiProvider(Cursor c) { |
| 158 | if (mAccount == null || c == null) { |
Vikram Aggarwal | 27e85f2 | 2012-03-05 16:29:17 -0800 | [diff] [blame] | 159 | return; |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 160 | } |
Vikram Aggarwal | 27d89ad | 2012-06-12 13:38:40 -0700 | [diff] [blame] | 161 | LogUtils.d(TAG, "Number of recents = %d", c.getCount()); |
Vikram Aggarwal | 27e85f2 | 2012-03-05 16:29:17 -0800 | [diff] [blame] | 162 | int i = 0; |
Paul Westbrook | 317348a | 2012-08-24 17:35:01 -0700 | [diff] [blame] | 163 | if (!c.moveToLast()) { |
| 164 | LogUtils.d(TAG, "Not able to move to last in recent labels cursor"); |
| 165 | return; |
| 166 | } |
Vikram Aggarwal | f991bee | 2012-08-24 09:11:52 -0700 | [diff] [blame] | 167 | // 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 Aggarwal | 27d89ad | 2012-06-12 13:38:40 -0700 | [diff] [blame] | 171 | final Folder folder = new Folder(c); |
Paul Westbrook | 5a64acd | 2012-09-07 10:30:04 -0700 | [diff] [blame] | 172 | final RecentFolderListEntry entry = new RecentFolderListEntry(folder); |
| 173 | mFolderCache.putElement(folder.uri.toString(), entry); |
Vikram Aggarwal | 27d89ad | 2012-06-12 13:38:40 -0700 | [diff] [blame] | 174 | LogUtils.v(TAG, "Account %s, Recent: %s", mAccount.name, folder.name); |
Vikram Aggarwal | f991bee | 2012-08-24 09:11:52 -0700 | [diff] [blame] | 175 | } while (c.moveToPrevious()); |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 176 | } |
| 177 | |
| 178 | /** |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 179 | * Marks the given folder as 'accessed' by the user interface, its entry is updated in the |
Vikram Aggarwal | 792ccba | 2012-03-27 13:46:57 -0700 | [diff] [blame] | 180 | * recent folder list, and the current time is written to the provider. This should never |
| 181 | * be called with a null folder. |
Marc Blank | 167faa8 | 2012-03-21 13:11:53 -0700 | [diff] [blame] | 182 | * @param folder the folder we touched |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 183 | */ |
Marc Blank | 2675dbc | 2012-04-03 10:17:13 -0700 | [diff] [blame] | 184 | public void touchFolder(Folder folder, Account account) { |
Vikram Aggarwal | 792ccba | 2012-03-27 13:46:57 -0700 | [diff] [blame] | 185 | // We haven't got a valid account yet, cannot proceed. |
Marc Blank | 2675dbc | 2012-04-03 10:17:13 -0700 | [diff] [blame] | 186 | 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 Aggarwal | 792ccba | 2012-03-27 13:46:57 -0700 | [diff] [blame] | 193 | } |
| 194 | assert (folder != null); |
Paul Westbrook | 5a64acd | 2012-09-07 10:30:04 -0700 | [diff] [blame] | 195 | final RecentFolderListEntry entry = new RecentFolderListEntry(folder); |
| 196 | mFolderCache.putElement(folder.uri.toString(), entry); |
Marc Blank | bc748ac | 2012-03-21 10:08:53 -0700 | [diff] [blame] | 197 | new StoreRecent(mAccount, folder).execute(); |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 198 | } |
| 199 | |
| 200 | /** |
Marc Blank | 167faa8 | 2012-03-21 13:11:53 -0700 | [diff] [blame] | 201 | * Generate a sorted list of recent folders, excluding the passed in folder (if any) and |
Vikram Aggarwal | f991bee | 2012-08-24 09:11:52 -0700 | [diff] [blame] | 202 | * default inbox for the current account. This must be called <em>after</em> |
Vikram Aggarwal | 025eba8 | 2012-05-08 10:45:30 -0700 | [diff] [blame] | 203 | * {@link #setCurrentAccount(Account)} has been called. |
Vikram Aggarwal | f991bee | 2012-08-24 09:11:52 -0700 | [diff] [blame] | 204 | * Returns a list of size {@value #MAX_RECENT_FOLDERS} or smaller. |
Vikram Aggarwal | 58cad2e | 2012-08-28 16:18:23 -0700 | [diff] [blame] | 205 | * @param excludedFolderUri the uri of folder to be excluded (typically the current folder) |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 206 | */ |
Vikram Aggarwal | 58cad2e | 2012-08-28 16:18:23 -0700 | [diff] [blame] | 207 | public ArrayList<Folder> getRecentFolderList(Uri excludedFolderUri) { |
Vikram Aggarwal | 025eba8 | 2012-05-08 10:45:30 -0700 | [diff] [blame] | 208 | final ArrayList<Uri> excludedUris = new ArrayList<Uri>(); |
Vikram Aggarwal | 58cad2e | 2012-08-28 16:18:23 -0700 | [diff] [blame] | 209 | if (excludedFolderUri != null) { |
| 210 | excludedUris.add(excludedFolderUri); |
Marc Blank | 167faa8 | 2012-03-21 13:11:53 -0700 | [diff] [blame] | 211 | } |
Vikram Aggarwal | 9da85df | 2012-05-09 15:34:23 -0700 | [diff] [blame] | 212 | final Uri defaultInbox = (mAccount == null) ? |
Vikram Aggarwal | f991bee | 2012-08-24 09:11:52 -0700 | [diff] [blame] | 213 | Uri.EMPTY : Settings.getDefaultInboxUri(mAccount.settings); |
Vikram Aggarwal | 1e57e67 | 2012-05-07 14:48:24 -0700 | [diff] [blame] | 214 | if (!defaultInbox.equals(Uri.EMPTY)) { |
Vikram Aggarwal | 1e57e67 | 2012-05-07 14:48:24 -0700 | [diff] [blame] | 215 | excludedUris.add(defaultInbox); |
Marc Blank | 167faa8 | 2012-03-21 13:11:53 -0700 | [diff] [blame] | 216 | } |
Paul Westbrook | 5a64acd | 2012-09-07 10:30:04 -0700 | [diff] [blame] | 217 | 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 Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 225 | } |
Vikram Aggarwal | f991bee | 2012-08-24 09:11:52 -0700 | [diff] [blame] | 226 | if (recentFolders.size() == MAX_RECENT_FOLDERS) { |
| 227 | break; |
| 228 | } |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 229 | } |
Paul Westbrook | 5a64acd | 2012-09-07 10:30:04 -0700 | [diff] [blame] | 230 | |
Vikram Aggarwal | f991bee | 2012-08-24 09:11:52 -0700 | [diff] [blame] | 231 | // Sort the values as the very last step. |
| 232 | Collections.sort(recentFolders, ALPHABET_IGNORECASE); |
Paul Westbrook | 5a64acd | 2012-09-07 10:30:04 -0700 | [diff] [blame] | 233 | |
Marc Blank | 167faa8 | 2012-03-21 13:11:53 -0700 | [diff] [blame] | 234 | return recentFolders; |
Vikram Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 235 | } |
Vikram Aggarwal | 7c401b7 | 2012-08-13 16:43:47 -0700 | [diff] [blame] | 236 | |
| 237 | /** |
| 238 | * Destroys this instance. The object is unusable after this has been called. |
| 239 | */ |
| 240 | public void destroy() { |
| 241 | mAccountObserver.unregisterAndDestroy(); |
| 242 | } |
Paul Westbrook | 5a64acd | 2012-09-07 10:30:04 -0700 | [diff] [blame] | 243 | |
| 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 Aggarwal | 1a4bcc0 | 2012-03-01 10:09:44 -0800 | [diff] [blame] | 264 | } |