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