RecentFolderList.java revision 6dde178687e85aaab9b3f8e9c124c3ab99b77d5a
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
17package com.android.mail.ui;
18
19import android.content.ContentResolver;
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.text.TextUtils;
24import android.os.AsyncTask;
25
26import com.android.mail.providers.Account;
27import com.android.mail.providers.Folder;
28import com.android.mail.providers.UIProvider;
29import com.android.mail.utils.LogUtils;
30import com.android.mail.utils.LruCache;
31
32import java.util.ArrayList;
33import java.util.Collections;
34import java.util.Comparator;
35import java.util.List;
36
37/**
38 * A self-updating list of folder canonical names for the N most recently touched folders, ordered
39 * from least-recently-touched to most-recently-touched. N is a fixed size determined upon
40 * creation.
41 *
42 * RecentFoldersCache returns lists of this type, and will keep them updated when observers are
43 * registered on them.
44 *
45 */
46public final class RecentFolderList {
47    private static final String LOG_TAG = new LogUtils().getLogTag();
48    /** The application context */
49    private final Context mContext;
50    /** The current account */
51    private Account mAccount = null;
52    /**
53     * Compare based on alphanumeric name of the folder, ignoring case.
54     */
55    private static final Comparator<Folder> ALPHABET_IGNORECASE = new Comparator<Folder>() {
56        @Override
57        public int compare(Folder lhs, Folder rhs) {
58            return lhs.name.compareToIgnoreCase(rhs.name);
59        }
60    };
61    private final LruCache<String, Folder> mFolderCache;
62    /**
63     *  We want to show five recent folders, and one space for the current folder (not displayed
64     *  to the user).
65     */
66    private final static int NUM_FOLDERS = 5 + 1;
67    /**
68     * Class to store the recent folder list asynchronously.
69     */
70    private class StoreRecent extends AsyncTask<ContentValues, Void, Void> {
71        final ContentResolver mResolver;
72        final Account mAccount;
73
74        public StoreRecent(Context context, Account account) {
75            mResolver = context.getContentResolver();
76            mAccount = account;
77        }
78
79        @Override
80        protected Void doInBackground(ContentValues... valuesArray) {
81            mResolver.update(mAccount.recentFolderListUri, valuesArray[0], null, null);
82            return null;
83        }
84    }
85
86    /**
87     * Create a Recent Folder List from the given account. This will query the UIProvider to
88     * retrieve the RecentFolderList from persistent storage (if any).
89     * @param account
90     */
91    public RecentFolderList(Context context) {
92        mFolderCache = new LruCache<String, Folder>(NUM_FOLDERS);
93        mContext = context;
94    }
95
96    /**
97     * Change the current account. This causes the recent label list to be written out to the
98     * provider. When a cursor over the recent folders for this account is available, the client
99     * <b>must</b> call {@link #loadFromUiProvider(Cursor)} with the updated cursor. Till then,
100     * the recent account list will be empty.
101     * @param account
102     */
103    public void setCurrentAccount(Account account) {
104        saveToUiProvider();
105        mAccount = account;
106        // At some point in the future, the load method will return and populate our cache with
107        // useful entries. But for now, the cache is invalid.
108        mFolderCache.clear();
109    }
110
111    /**
112     * Load the account information from the UI provider given the cursor over the recent folders.
113     * @param data a cursor over the recent folders.
114     */
115    public void loadFromUiProvider(Cursor data) {
116        if (mAccount == null || mAccount.recentFolderListUri == null || data == null
117                || data.getCount() <= 0)
118            return;
119        int i = 0;
120        while (data.moveToNext()) {
121            assert (data.getColumnCount() == UIProvider.FOLDERS_PROJECTION.length);
122            Folder folder = new Folder(data);
123            mFolderCache.putElement(folder.id, folder);
124            i++;
125            if (i >= NUM_FOLDERS)
126                break;
127        }
128    }
129
130    /**
131     * Marks the given folder as 'accessed' by the user interface, and its entry is updated in the
132     * recent folder list.
133     * @param folder the folder we have changed to.
134     */
135    public void touchFolder(Folder folder) {
136        mFolderCache.putElement(folder.id, folder);
137        // Update the UiProvider with the current recent folder list.
138        // TODO(viki): Perhaps not do this on every touch. This is excessive.
139        saveToUiProvider();
140    }
141
142    /**
143     * Requests the UIProvider to save this RecentFolderList to persistent storage.
144     */
145    private void saveToUiProvider() {
146        if (mAccount == null || mFolderCache.isEmpty() || mAccount.recentFolderListUri == null)
147            return;
148        // TODO: Remove this test
149        if (TextUtils.equals("null", mAccount.recentFolderListUri.toString())) {
150            LogUtils.d(LOG_TAG, "recent folder list uri was null for account " + mAccount.name);
151            return;
152        }
153        // Write the current recent folder list into the account.
154        // Store the ID of the folder and the last touched timestamp.
155        ContentValues values = new ContentValues();
156        // TODO(viki): Fix the timestamps here, and put real timestamps rather than garbage.
157        final long now = System.currentTimeMillis();
158        for (String id : mFolderCache.keySet()) {
159            values.put(id, now);
160        }
161        // Store the values in the background.
162        new StoreRecent(mContext, mAccount).execute(values);
163    }
164
165    /**
166     * Generate a sorted array of recent folders, excluding the specified folders.
167     * @param exclude the folders to be excluded.
168     */
169    public Folder[] getSortedArray(Folder exclude) {
170        final int spaceForCurrentFolder =
171                (exclude != null && mFolderCache.getElement(exclude.id) != null)
172                        ? 1 : 0;
173        final int numRecents = mFolderCache.size() - spaceForCurrentFolder;
174        final Folder[] folders = new Folder[numRecents];
175        int i = 0;
176        final List<Folder> recent = new ArrayList<Folder>(mFolderCache.values());
177        Collections.sort(recent, ALPHABET_IGNORECASE);
178        for (Folder f : recent) {
179            if (exclude == null || !TextUtils.equals(f.id, exclude.id)) {
180                folders[i++] = f;
181            }
182        }
183        return folders;
184    }
185}
186