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