Folder.java revision ab902e8c594515dff836d45b96fe480ed2d01f13
1/*******************************************************************************
2 *      Copyright (C) 2012 Google Inc.
3 *      Licensed to The Android Open Source Project.
4 *
5 *      Licensed under the Apache License, Version 2.0 (the "License");
6 *      you may not use this file except in compliance with the License.
7 *      You may obtain a copy of the License at
8 *
9 *           http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *      Unless required by applicable law or agreed to in writing, software
12 *      distributed under the License is distributed on an "AS IS" BASIS,
13 *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *      See the License for the specific language governing permissions and
15 *      limitations under the License.
16 *******************************************************************************/
17
18package com.android.mail.providers;
19
20import android.content.Context;
21import android.database.Cursor;
22import android.graphics.Color;
23import android.graphics.drawable.PaintDrawable;
24import android.net.Uri;
25import android.net.Uri.Builder;
26import android.os.Parcel;
27import android.os.Parcelable;
28import android.os.Parcelable.Creator;
29import android.text.TextUtils;
30import android.view.View;
31
32import com.android.mail.utils.LogUtils;
33import com.google.common.collect.Maps;
34
35import java.util.Collection;
36import java.util.Map;
37import java.util.regex.Pattern;
38
39/**
40 * A folder is a collection of conversations, and perhaps other folders.
41 */
42public class Folder implements Parcelable {
43    /**
44     *
45     */
46    private static final String FOLDER_UNINITIALIZED = "Uninitialized!";
47
48    // Try to match the order of members with the order of constants in UIProvider.
49
50    /**
51     * Unique id of this folder.
52     */
53    public String id;
54
55    /**
56     * The content provider URI that returns this folder for this account.
57     */
58    public Uri uri;
59
60    /**
61     * The human visible name for this folder.
62     */
63    public String name;
64
65    /**
66     * The possible capabilities that this folder supports.
67     */
68    public int capabilities;
69
70    /**
71     * Whether or not this folder has children folders.
72     */
73    public boolean hasChildren;
74
75    /**
76     * How large the synchronization window is: how many days worth of data is retained on the
77     * device.
78     */
79    public int syncWindow;
80
81    /**
82     * The content provider URI to return the list of conversations in this
83     * folder.
84     */
85    public Uri conversationListUri;
86
87    /**
88     * The content provider URI to return the list of child folders of this folder.
89     */
90    public Uri childFoldersListUri;
91
92    /**
93     * The number of messages that are unread in this folder.
94     */
95    public int unreadCount;
96
97    /**
98     * The total number of messages in this folder.
99     */
100    public int totalCount;
101
102    /**
103     * The content provider URI to force a refresh of this folder.
104     */
105    public Uri refreshUri;
106
107    /**
108     * The current sync status of the folder
109     */
110    public int syncStatus;
111
112    /**
113     * The result of the last sync for this folder
114     */
115    public int lastSyncResult;
116
117    /**
118     * Folder type. 0 is default.
119     */
120    public int type;
121
122    /**
123     * Icon for this folder; 0 implies no icon.
124     */
125    public long iconResId;
126
127    public String bgColor;
128    public String fgColor;
129
130    /**
131     * The content provider URI to request additional conversations
132     */
133    public Uri loadMoreUri;
134
135    /**
136     * Total number of members that comprise an instance of a folder. This is
137     * the number of members that need to be serialized or parceled. We don't serialize
138     * the _id field, so subtract 1.
139     */
140    private static final int NUMBER_MEMBERS = UIProvider.FOLDERS_PROJECTION.length - 1;
141
142    /**
143     * Used only for debugging.
144     */
145    private static final String LOG_TAG = new LogUtils().getLogTag();
146
147    /**
148     * Examples of expected format for the joined folder strings
149     *
150     * Example of a joined folder string:
151     *       630107622^*^^i^*^^i^*^0
152     *       <id>^*^<canonical name>^*^<name>^*^<color index>
153     *
154     * The sqlite queries will return a list of folder strings separated with "^**^"
155     * Example of a query result:
156     *     630107622^*^^i^*^^i^*^0^**^630107626^*^^u^*^^u^*^0^**^630107627^*^^f^*^^f^*^0
157     */
158    private static final String FOLDER_COMPONENT_SEPARATOR = "^*^";
159    private static final Pattern FOLDER_COMPONENT_SEPARATOR_PATTERN =
160            Pattern.compile("\\^\\*\\^");
161
162    public static final String FOLDER_SEPARATOR = "^**^";
163    public static final Pattern FOLDER_SEPARATOR_PATTERN =
164            Pattern.compile("\\^\\*\\*\\^");
165
166    public Folder(Parcel in) {
167        assert (in.dataSize() == NUMBER_MEMBERS);
168        id = in.readString();
169        uri = in.readParcelable(null);
170        name = in.readString();
171        capabilities = in.readInt();
172        // 1 for true, 0 for false.
173        hasChildren = in.readInt() == 1;
174        syncWindow = in.readInt();
175        conversationListUri = in.readParcelable(null);
176        childFoldersListUri = in.readParcelable(null);
177        unreadCount = in.readInt();
178        totalCount = in.readInt();
179        refreshUri = in.readParcelable(null);
180        syncStatus = in.readInt();
181        lastSyncResult = in.readInt();
182        type = in.readInt();
183        iconResId = in.readLong();
184        bgColor = in.readString();
185        fgColor = in.readString();
186        loadMoreUri = in.readParcelable(null);
187     }
188
189    public Folder(Cursor cursor) {
190        assert (cursor.getColumnCount() == NUMBER_MEMBERS);
191        id = cursor.getString(UIProvider.FOLDER_ID_COLUMN);
192        uri = Uri.parse(cursor.getString(UIProvider.FOLDER_URI_COLUMN));
193        name = cursor.getString(UIProvider.FOLDER_NAME_COLUMN);
194        capabilities = cursor.getInt(UIProvider.FOLDER_CAPABILITIES_COLUMN);
195        // 1 for true, 0 for false.
196        hasChildren = cursor.getInt(UIProvider.FOLDER_HAS_CHILDREN_COLUMN) == 1;
197        syncWindow = cursor.getInt(UIProvider.FOLDER_SYNC_WINDOW_COLUMN);
198        String convList = cursor.getString(UIProvider.FOLDER_CONVERSATION_LIST_URI_COLUMN);
199        conversationListUri = !TextUtils.isEmpty(convList) ? Uri.parse(convList) : null;
200        String childList = cursor.getString(UIProvider.FOLDER_CHILD_FOLDERS_LIST_COLUMN);
201        childFoldersListUri = (hasChildren && !TextUtils.isEmpty(childList)) ? Uri.parse(childList)
202                : null;
203        unreadCount = cursor.getInt(UIProvider.FOLDER_UNREAD_COUNT_COLUMN);
204        totalCount = cursor.getInt(UIProvider.FOLDER_TOTAL_COUNT_COLUMN);
205        String refresh = cursor.getString(UIProvider.FOLDER_REFRESH_URI_COLUMN);
206        refreshUri = !TextUtils.isEmpty(refresh) ? Uri.parse(refresh) : null;
207        syncStatus = cursor.getInt(UIProvider.FOLDER_SYNC_STATUS_COLUMN);
208        lastSyncResult = cursor.getInt(UIProvider.FOLDER_LAST_SYNC_RESULT_COLUMN);
209        type = cursor.getInt(UIProvider.FOLDER_TYPE_COLUMN);
210        iconResId = cursor.getLong(UIProvider.FOLDER_ICON_RES_ID_COLUMN);
211        bgColor = cursor.getString(UIProvider.FOLDER_BG_COLOR_COLUMN);
212        fgColor = cursor.getString(UIProvider.FOLDER_FG_COLOR_COLUMN);
213        String loadMore = cursor.getString(UIProvider.FOLDER_LOAD_MORE_URI_COLUMN);
214        loadMoreUri = !TextUtils.isEmpty(loadMore) ? Uri.parse(loadMore) : null;
215    }
216
217    @Override
218    public void writeToParcel(Parcel dest, int flags) {
219        dest.writeString(id);
220        dest.writeParcelable(uri, 0);
221        dest.writeString(name);
222        dest.writeInt(capabilities);
223        // 1 for true, 0 for false.
224        dest.writeInt(hasChildren ? 1 : 0);
225        dest.writeInt(syncWindow);
226        dest.writeParcelable(conversationListUri, 0);
227        dest.writeParcelable(childFoldersListUri, 0);
228        dest.writeInt(unreadCount);
229        dest.writeInt(totalCount);
230        dest.writeParcelable(refreshUri, 0);
231        dest.writeInt(syncStatus);
232        dest.writeInt(lastSyncResult);
233        dest.writeInt(type);
234        dest.writeLong(iconResId);
235        dest.writeString(bgColor);
236        dest.writeString(fgColor);
237        dest.writeParcelable(loadMoreUri, 0);
238    }
239
240    /**
241     * Return a serialized String for this folder.
242     */
243    public synchronized String serialize(){
244        StringBuilder out = new StringBuilder();
245        out.append(id).append(FOLDER_COMPONENT_SEPARATOR);
246        out.append(uri).append(FOLDER_COMPONENT_SEPARATOR);
247        out.append(name).append(FOLDER_COMPONENT_SEPARATOR);
248        out.append(capabilities).append(FOLDER_COMPONENT_SEPARATOR);
249        out.append(hasChildren ? "1": "0").append(FOLDER_COMPONENT_SEPARATOR);
250        out.append(syncWindow).append(FOLDER_COMPONENT_SEPARATOR);
251        out.append(conversationListUri).append(FOLDER_COMPONENT_SEPARATOR);
252        out.append(childFoldersListUri).append(FOLDER_COMPONENT_SEPARATOR);
253        out.append(unreadCount).append(FOLDER_COMPONENT_SEPARATOR);
254        out.append(totalCount).append(FOLDER_COMPONENT_SEPARATOR);
255        out.append(refreshUri).append(FOLDER_COMPONENT_SEPARATOR);
256        out.append(syncStatus).append(FOLDER_COMPONENT_SEPARATOR);
257        out.append(lastSyncResult).append(FOLDER_COMPONENT_SEPARATOR);
258        out.append(type).append(FOLDER_COMPONENT_SEPARATOR);
259        out.append(iconResId).append(FOLDER_COMPONENT_SEPARATOR);
260        out.append(bgColor).append(FOLDER_COMPONENT_SEPARATOR);
261        out.append(fgColor).append(FOLDER_COMPONENT_SEPARATOR);
262        out.append(loadMoreUri);
263        return out.toString();
264    }
265
266    /**
267     * Construct a folder that queries for search results. Do not call on the UI
268     * thread.
269     */
270    public static Folder forSearchResults(Account account, String query, Context context) {
271        Folder searchFolder = null;
272        if (account.searchUri != null) {
273            Builder searchBuilder = account.searchUri.buildUpon();
274            searchBuilder.appendQueryParameter(UIProvider.SearchQueryParameters.QUERY, query);
275            Uri searchUri = searchBuilder.build();
276            Cursor folderCursor = context.getContentResolver().query(searchUri,
277                    UIProvider.FOLDERS_PROJECTION, null, null, null);
278            if (folderCursor != null) {
279                folderCursor.moveToFirst();
280                searchFolder = new Folder(folderCursor);
281            }
282        }
283        return searchFolder;
284    }
285
286    /**
287     * Construct a new Folder instance from a previously serialized string.
288     * @param serializedFolder string obtained from {@link #serialize()} on a valid folder.
289     */
290    public Folder(String serializedFolder) {
291        String[] folderMembers = TextUtils.split(serializedFolder,
292                FOLDER_COMPONENT_SEPARATOR_PATTERN);
293        if (folderMembers.length != NUMBER_MEMBERS) {
294            throw new IllegalArgumentException(
295                    "Folder de-serializing failed. Wrong number of members detected."
296                            + folderMembers.length);
297        }
298        id = folderMembers[0];
299        uri = Uri.parse(folderMembers[1]);
300        name = folderMembers[2];
301        capabilities = Integer.valueOf(folderMembers[3]);
302        // 1 for true, 0 for false
303        hasChildren = folderMembers[4] == "1";
304        syncWindow = Integer.valueOf(folderMembers[5]);
305        String convList = folderMembers[6];
306        conversationListUri = !TextUtils.isEmpty(convList) ? Uri.parse(convList) : null;
307        String childList = folderMembers[7];
308        childFoldersListUri = (hasChildren && !TextUtils.isEmpty(childList)) ? Uri.parse(childList)
309                : null;
310        unreadCount = Integer.valueOf(folderMembers[8]);
311        totalCount = Integer.valueOf(folderMembers[9]);
312        String refresh = folderMembers[10];
313        refreshUri = !TextUtils.isEmpty(refresh) ? Uri.parse(refresh) : null;
314        syncStatus = Integer.valueOf(folderMembers[11]);
315        lastSyncResult = Integer.valueOf(folderMembers[12]);
316        type = Integer.valueOf(folderMembers[13]);
317        iconResId = Long.valueOf(folderMembers[14]);
318        bgColor = folderMembers[15];
319        fgColor = folderMembers[16];
320        String loadMore = folderMembers[17];
321        loadMoreUri = !TextUtils.isEmpty(loadMore) ? Uri.parse(loadMore) : null;
322    }
323
324    /**
325     * Constructor that leaves everything uninitialized. For use only by {@link #serialize()}
326     * which is responsible for filling in all the fields
327     */
328    public Folder() {
329        name = FOLDER_UNINITIALIZED;
330    }
331
332    @SuppressWarnings("hiding")
333    public static final Creator<Folder> CREATOR = new Creator<Folder>() {
334        @Override
335        public Folder createFromParcel(Parcel source) {
336            return new Folder(source);
337        }
338
339        @Override
340        public Folder[] newArray(int size) {
341            return new Folder[size];
342        }
343    };
344
345    @Override
346    public int describeContents() {
347        // Return a sort of version number for this parcelable folder. Starting with zero.
348        return 0;
349    }
350
351    /**
352     * Create a Folder map from a string of serialized folders. This can only be done on the output
353     * of {@link #serialize(Map)}.
354     * @param serializedFolder A string obtained from {@link #serialize(Map)}
355     * @return a Map of folder name to folder.
356     */
357    public static Map<String, Folder> parseFoldersFromString(String serializedFolder) {
358        LogUtils.d(LOG_TAG, "folder query result: %s", serializedFolder);
359
360        Map<String, Folder> folderMap = Maps.newHashMap();
361        if (serializedFolder == null || serializedFolder == "") {
362            return folderMap;
363        }
364        String[] folderPieces = TextUtils.split(
365                serializedFolder, FOLDER_COMPONENT_SEPARATOR_PATTERN);
366        for (int i = 0, n = folderPieces.length; i < n; i++) {
367            Folder folder = new Folder(folderPieces[i]);
368            if (folder.name != FOLDER_UNINITIALIZED) {
369                folderMap.put(folder.name, folder);
370            }
371        }
372        return folderMap;
373    }
374
375    /**
376     * Returns a boolean indicating whether network activity (sync) is occuring for this folder.
377     */
378    public boolean isSyncInProgress() {
379        return 0 != (syncStatus & (UIProvider.SyncStatus.BACKGROUND_SYNC |
380                UIProvider.SyncStatus.USER_REFRESH |
381                UIProvider.SyncStatus.USER_QUERY |
382                UIProvider.SyncStatus.USER_MORE_RESULTS));
383    }
384
385    /**
386     * Serialize the given list of folders
387     * @param folderMap A valid map of folder names to Folders
388     * @return a string containing a serialized output of folder maps.
389     */
390    public static String serialize(Map<String, Folder> folderMap) {
391        Collection<Folder> folderCollection = folderMap.values();
392        Folder[] folderList = folderCollection.toArray(new Folder[]{} );
393        int numFolders = folderList.length;
394        StringBuilder result = new StringBuilder();
395        for (int i = 0; i < numFolders; i++) {
396          if (i > 0) {
397              result.append(FOLDER_SEPARATOR);
398          }
399          Folder folder = folderList[i];
400          result.append(folder.serialize());
401        }
402        return result.toString();
403    }
404
405    public boolean supportsCapability(int capability) {
406        return (capabilities & capability) != 0;
407    }
408
409    // Show black text on a transparent swatch for system folders, effectively hiding the
410    // swatch (see bug 2431925).
411    public static void setFolderBlockColor(Folder folder, View colorBlock) {
412        final boolean showBg = !TextUtils.isEmpty(folder.bgColor);
413        final int backgroundColor = showBg ? Color.parseColor(folder.bgColor) : 0;
414
415        if (!showBg) {
416            colorBlock.setBackgroundDrawable(null);
417        } else {
418            PaintDrawable paintDrawable = new PaintDrawable();
419            paintDrawable.getPaint().setColor(backgroundColor);
420            colorBlock.setBackgroundDrawable(paintDrawable);
421        }
422    }
423
424    /**
425     * Return if the type of the folder matches a provider defined folder.
426     */
427    public static boolean isProviderFolder(Folder folder) {
428        int type = folder.type;
429        return type == UIProvider.FolderType.DEFAULT ||
430               type == UIProvider.FolderType.INBOX ||
431               type == UIProvider.FolderType.DRAFT ||
432               type == UIProvider.FolderType.OUTBOX ||
433               type == UIProvider.FolderType.SENT ||
434               type == UIProvider.FolderType.TRASH ||
435               type == UIProvider.FolderType.SPAM;
436    }
437}
438