RecentFolderList.java revision bc748acc02add75ccd04691931464547d998ec08
11a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal/**
21a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * Copyright (c) 2011, Google Inc.
31a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal *
41a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * Licensed under the Apache License, Version 2.0 (the "License");
51a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * you may not use this file except in compliance with the License.
61a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * You may obtain a copy of the License at
71a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal *
81a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal *     http://www.apache.org/licenses/LICENSE-2.0
91a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal *
101a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * Unless required by applicable law or agreed to in writing, software
111a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * distributed under the License is distributed on an "AS IS" BASIS,
121a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * See the License for the specific language governing permissions and
141a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * limitations under the License.
151a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal */
161a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal
171a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwalpackage com.android.mail.ui;
181a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal
1927e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwalimport android.content.ContentValues;
2027e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwalimport android.content.Context;
2127e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwalimport android.database.Cursor;
22bc748acc02add75ccd04691931464547d998ec08Marc Blankimport android.net.Uri;
23fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwalimport android.os.AsyncTask;
241a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal
251a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwalimport com.android.mail.providers.Account;
261a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwalimport com.android.mail.providers.Folder;
271a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwalimport com.android.mail.utils.LogUtils;
281a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwalimport com.android.mail.utils.LruCache;
291a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal
301a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwalimport java.util.ArrayList;
313232a96e0ea88741dc39acf17d49e9c22b61c707Marc Blankimport java.util.Collections;
321a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwalimport java.util.Comparator;
333232a96e0ea88741dc39acf17d49e9c22b61c707Marc Blankimport java.util.List;
341a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal
351a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal/**
361a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * A self-updating list of folder canonical names for the N most recently touched folders, ordered
371a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * from least-recently-touched to most-recently-touched. N is a fixed size determined upon
381a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * creation.
391a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal *
401a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * RecentFoldersCache returns lists of this type, and will keep them updated when observers are
411a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal * registered on them.
421a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal *
431a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal */
441a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwalpublic final class RecentFolderList {
45bc748acc02add75ccd04691931464547d998ec08Marc Blank    private static final String TAG = "RecentFolderList";
4627e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal    /** The application context */
4727e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal    private final Context mContext;
4827e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal    /** The current account */
49ec5cbf79b825c6f96d45e85015319c66470b7e57Vikram Aggarwal    private Account mAccount = null;
50bc748acc02add75ccd04691931464547d998ec08Marc Blank
51bc748acc02add75ccd04691931464547d998ec08Marc Blank    private final LruCache<String, Folder> mFolderCache;
52bc748acc02add75ccd04691931464547d998ec08Marc Blank    /**
53bc748acc02add75ccd04691931464547d998ec08Marc Blank     *  We want to show five recent folders, and one space for the current folder (not displayed
54bc748acc02add75ccd04691931464547d998ec08Marc Blank     *  to the user).
55bc748acc02add75ccd04691931464547d998ec08Marc Blank     */
56bc748acc02add75ccd04691931464547d998ec08Marc Blank    private final static int NUM_FOLDERS = 5 + 1;
57bc748acc02add75ccd04691931464547d998ec08Marc Blank
581a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal    /**
591a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal     * Compare based on alphanumeric name of the folder, ignoring case.
601a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal     */
611a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal    private static final Comparator<Folder> ALPHABET_IGNORECASE = new Comparator<Folder>() {
621a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        @Override
631a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        public int compare(Folder lhs, Folder rhs) {
641a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal            return lhs.name.compareToIgnoreCase(rhs.name);
651a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        }
661a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal    };
67fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal    /**
68fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal     * Class to store the recent folder list asynchronously.
69fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal     */
70bc748acc02add75ccd04691931464547d998ec08Marc Blank    private class StoreRecent extends AsyncTask<Void, Void, Void> {
71c972b18618baa79b2825b5661d7cd11fffb1d3a1Marc Blank        final Account mAccount;
72bc748acc02add75ccd04691931464547d998ec08Marc Blank        final Folder mFolder;
73fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal
74bc748acc02add75ccd04691931464547d998ec08Marc Blank        public StoreRecent(Account account, Folder folder) {
75c972b18618baa79b2825b5661d7cd11fffb1d3a1Marc Blank            mAccount = account;
76bc748acc02add75ccd04691931464547d998ec08Marc Blank            mFolder = folder;
77fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal        }
78fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal
79fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal        @Override
80bc748acc02add75ccd04691931464547d998ec08Marc Blank        protected Void doInBackground(Void... v) {
81bc748acc02add75ccd04691931464547d998ec08Marc Blank            Uri uri = mAccount.recentFolderListUri;
82bc748acc02add75ccd04691931464547d998ec08Marc Blank            if (uri != null) {
83bc748acc02add75ccd04691931464547d998ec08Marc Blank                ContentValues values = new ContentValues();
84bc748acc02add75ccd04691931464547d998ec08Marc Blank                values.put(mFolder.uri.toString(), System.currentTimeMillis());
85bc748acc02add75ccd04691931464547d998ec08Marc Blank                // TODO: Remove when well tested
86bc748acc02add75ccd04691931464547d998ec08Marc Blank                LogUtils.i(TAG, "Save: " + mFolder.name);
87bc748acc02add75ccd04691931464547d998ec08Marc Blank                mContext.getContentResolver().update(uri, values, null, null);
88bc748acc02add75ccd04691931464547d998ec08Marc Blank            }
89fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal            return null;
90fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal        }
91fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal    }
921a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal
931a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal    /**
941a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal     * Create a Recent Folder List from the given account. This will query the UIProvider to
951a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal     * retrieve the RecentFolderList from persistent storage (if any).
961a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal     * @param account
971a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal     */
98ec5cbf79b825c6f96d45e85015319c66470b7e57Vikram Aggarwal    public RecentFolderList(Context context) {
9927e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal        mFolderCache = new LruCache<String, Folder>(NUM_FOLDERS);
100fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal        mContext = context;
10127e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal    }
10227e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal
103ec5cbf79b825c6f96d45e85015319c66470b7e57Vikram Aggarwal    /**
104ec5cbf79b825c6f96d45e85015319c66470b7e57Vikram Aggarwal     * Change the current account. This causes the recent label list to be written out to the
105fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal     * provider. When a cursor over the recent folders for this account is available, the client
106fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal     * <b>must</b> call {@link #loadFromUiProvider(Cursor)} with the updated cursor. Till then,
107fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal     * the recent account list will be empty.
108ec5cbf79b825c6f96d45e85015319c66470b7e57Vikram Aggarwal     * @param account
109ec5cbf79b825c6f96d45e85015319c66470b7e57Vikram Aggarwal     */
110fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal    public void setCurrentAccount(Account account) {
11127e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal        mAccount = account;
112fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal        // At some point in the future, the load method will return and populate our cache with
113fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal        // useful entries. But for now, the cache is invalid.
114fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal        mFolderCache.clear();
11527e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal    }
11627e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal
11727e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal    /**
118fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal     * Load the account information from the UI provider given the cursor over the recent folders.
119fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal     * @param data a cursor over the recent folders.
12027e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal     */
121fa4b47e4a1962010ec3c8ea7476ac1e701f461b4Vikram Aggarwal    public void loadFromUiProvider(Cursor data) {
122bc748acc02add75ccd04691931464547d998ec08Marc Blank        if (mAccount == null || data == null) {
12327e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal            return;
124bc748acc02add75ccd04691931464547d998ec08Marc Blank        }
12527e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal        int i = 0;
12627e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal        while (data.moveToNext()) {
12727e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal            Folder folder = new Folder(data);
128bc748acc02add75ccd04691931464547d998ec08Marc Blank            String folderUriString = folder.uri.toString();
129bc748acc02add75ccd04691931464547d998ec08Marc Blank            mFolderCache.putElement(folderUriString, folder);
130bc748acc02add75ccd04691931464547d998ec08Marc Blank            // TODO: Remove when well tested
131bc748acc02add75ccd04691931464547d998ec08Marc Blank            LogUtils.i(TAG, "Account " + mAccount.name + ", Recent: " + folder.name);
13227e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal            i++;
13327e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal            if (i >= NUM_FOLDERS)
13427e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal                break;
13527e85f244604c8de53b76b135e0dd6f2bf3cad96Vikram Aggarwal        }
1361a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal    }
1371a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal
1381a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal    /**
139bc748acc02add75ccd04691931464547d998ec08Marc Blank     * Marks the given folder as 'accessed' by the user interface, its entry is updated in the
140bc748acc02add75ccd04691931464547d998ec08Marc Blank     * recent folder list, and the current time is written to the provider
1411a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal     * @param folder the folder we have changed to.
1421a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal     */
143ec5cbf79b825c6f96d45e85015319c66470b7e57Vikram Aggarwal    public void touchFolder(Folder folder) {
1447e5de7a9825120dcc6c94230ec5cd61b178fedf2Mindy Pereira        mFolderCache.putElement(folder.uri.toString(), folder);
145bc748acc02add75ccd04691931464547d998ec08Marc Blank        new StoreRecent(mAccount, folder).execute();
1461a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal    }
1471a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal
1481a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal    /**
149ec5cbf79b825c6f96d45e85015319c66470b7e57Vikram Aggarwal     * Generate a sorted array of recent folders, excluding the specified folders.
150bc748acc02add75ccd04691931464547d998ec08Marc Blank     * @param exclude the folder to be excluded.
1511a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal     */
152ec5cbf79b825c6f96d45e85015319c66470b7e57Vikram Aggarwal    public Folder[] getSortedArray(Folder exclude) {
153bc748acc02add75ccd04691931464547d998ec08Marc Blank        // TODO: Need to exclude default inbox folder as well!
1541a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        final int spaceForCurrentFolder =
1557e5de7a9825120dcc6c94230ec5cd61b178fedf2Mindy Pereira                (exclude != null && mFolderCache.getElement(exclude.uri.toString()) != null)
1561a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal                        ? 1 : 0;
1571a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        final int numRecents = mFolderCache.size() - spaceForCurrentFolder;
1581a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        final Folder[] folders = new Folder[numRecents];
1591a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        int i = 0;
1601a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        final List<Folder> recent = new ArrayList<Folder>(mFolderCache.values());
1611a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        Collections.sort(recent, ALPHABET_IGNORECASE);
1621a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        for (Folder f : recent) {
1637e5de7a9825120dcc6c94230ec5cd61b178fedf2Mindy Pereira            if (exclude == null || !f.uri.equals(exclude.uri)) {
1641a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal                folders[i++] = f;
1651a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal            }
1661a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        }
1671a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal        return folders;
1681a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal    }
1691a4bcc08699356eeaa25d8ad144a1a00cea76cd0Vikram Aggarwal}
170