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.PorterDuff;
23import android.graphics.drawable.Drawable;
24import android.graphics.drawable.PaintDrawable;
25import android.graphics.drawable.StateListDrawable;
26import android.net.Uri;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.text.TextUtils;
30import android.util.StateSet;
31import android.view.View;
32import android.widget.ImageView;
33
34import com.android.mail.R;
35import com.android.mail.content.CursorCreator;
36import com.android.mail.content.ObjectCursorLoader;
37import com.android.mail.providers.UIProvider.FolderType;
38import com.android.mail.utils.FolderUri;
39import com.android.mail.utils.LogTag;
40import com.android.mail.utils.LogUtils;
41import com.android.mail.utils.Utils;
42import com.google.common.annotations.VisibleForTesting;
43import com.google.common.base.Objects;
44
45import java.util.Collection;
46import java.util.Collections;
47import java.util.HashMap;
48import java.util.List;
49import java.util.regex.Pattern;
50
51/**
52 * A folder is a collection of conversations, and perhaps other folders.
53 */
54// TODO: make most of these fields final
55public class Folder implements Parcelable, Comparable<Folder> {
56
57    @Deprecated
58    public static final String SPLITTER = "^*^";
59    @Deprecated
60    private static final Pattern SPLITTER_REGEX = Pattern.compile("\\^\\*\\^");
61
62    private static final String FOLDER_UNINITIALIZED = "Uninitialized!";
63
64    // TODO: remove this once we figure out which folder is returning a "null" string as the
65    // conversation list uri
66    private static final String NULL_STRING_URI = "null";
67    private static final String LOG_TAG = LogTag.getLogTag();
68
69    // Try to match the order of members with the order of constants in UIProvider.
70
71    /**
72     * Unique id of this folder.
73     */
74    public int id;
75
76    /**
77     * Persistent (across installations) id of this folder.
78     */
79    public String persistentId;
80
81    /**
82     * The content provider URI that returns this folder for this account.
83     */
84    public FolderUri folderUri;
85
86    /**
87     * The human visible name for this folder.
88     */
89    public String name;
90
91    /**
92     * The possible capabilities that this folder supports.
93     */
94    public int capabilities;
95
96    /**
97     * Whether or not this folder has children folders.
98     */
99    public boolean hasChildren;
100
101    /**
102     * How large the synchronization window is: how many days worth of data is retained on the
103     * device.
104     */
105    public int syncWindow;
106
107    /**
108     * The content provider URI to return the list of conversations in this
109     * folder.
110     */
111    public Uri conversationListUri;
112
113    /**
114     * The content provider URI to return the list of child folders of this folder.
115     */
116    public Uri childFoldersListUri;
117
118    /**
119     * The number of messages that are unseen in this folder.
120     */
121    public int unseenCount;
122
123    /**
124     * The number of messages that are unread in this folder.
125     */
126    public int unreadCount;
127
128    /**
129     * The total number of messages in this folder.
130     */
131    public int totalCount;
132
133    /**
134     * The content provider URI to force a refresh of this folder.
135     */
136    public Uri refreshUri;
137
138    /**
139     * The current sync status of the folder
140     */
141    public int syncStatus;
142
143    /**
144     * A packed integer containing the last synced result, and the request code. The
145     * value is (requestCode << 4) | syncResult
146     * syncResult is a value from {@link UIProvider.LastSyncResult}
147     * requestCode is a value from: {@link UIProvider.SyncStatus},
148     */
149    public int lastSyncResult;
150
151    /**
152     * Folder type bit mask. 0 is default.
153     * @see FolderType
154     */
155    public int type;
156
157    /**
158     * Icon for this folder; 0 implies no icon.
159     */
160    public int iconResId;
161
162    /**
163     * Notification icon for this folder; 0 implies no icon.
164     */
165    public int notificationIconResId;
166
167    public String bgColor;
168    public String fgColor;
169
170    private int bgColorInt;
171    private int fgColorInt;
172
173    /**
174     * The content provider URI to request additional conversations
175     */
176    public Uri loadMoreUri;
177
178    /**
179     * The possibly empty name of this folder with full hierarchy.
180     * The expected format is: parent/folder1/folder2/folder3/folder4
181     */
182    public String hierarchicalDesc;
183
184    /**
185     * Parent folder of this folder, or null if there is none.
186     */
187    public Uri parent;
188
189    /**
190     * The time at which the last message was received.
191     */
192    public long lastMessageTimestamp;
193
194    /**
195     * A string of unread senders sorted by date, so we don't have to fetch this in multiple queries
196     */
197    public String unreadSenders;
198
199    /** An immutable, empty conversation list */
200    public static final Collection<Folder> EMPTY = Collections.emptyList();
201
202    public static final class Builder {
203        private int mId;
204        private String mPersistentId;
205        private Uri mUri;
206        private String mName;
207        private int mCapabilities;
208        private boolean mHasChildren;
209        private int mSyncWindow;
210        private Uri mConversationListUri;
211        private Uri mChildFoldersListUri;
212        private int mUnseenCount;
213        private int mUnreadCount;
214        private int mTotalCount;
215        private Uri mRefreshUri;
216        private int mSyncStatus;
217        private int mLastSyncResult;
218        private int mType;
219        private int mIconResId;
220        private int mNotificationIconResId;
221        private String mBgColor;
222        private String mFgColor;
223        private Uri mLoadMoreUri;
224        private String mHierarchicalDesc;
225        private Uri mParent;
226        private long mLastMessageTimestamp;
227        private String mUnreadSenders;
228
229        public Folder build() {
230            return new Folder(mId, mPersistentId, mUri, mName, mCapabilities,
231                    mHasChildren, mSyncWindow, mConversationListUri, mChildFoldersListUri,
232                    mUnseenCount, mUnreadCount, mTotalCount, mRefreshUri, mSyncStatus,
233                    mLastSyncResult, mType, mIconResId, mNotificationIconResId, mBgColor,
234                    mFgColor, mLoadMoreUri, mHierarchicalDesc, mParent,
235                    mLastMessageTimestamp, mUnreadSenders);
236        }
237
238        public Builder setId(final int id) {
239            mId = id;
240            return this;
241        }
242        public Builder setPersistentId(final String persistentId) {
243            mPersistentId = persistentId;
244            return this;
245        }
246        public Builder setUri(final Uri uri) {
247            mUri = uri;
248            return this;
249        }
250        public Builder setName(final String name) {
251            mName = name;
252            return this;
253        }
254        public Builder setCapabilities(final int capabilities) {
255            mCapabilities = capabilities;
256            return this;
257        }
258        public Builder setHasChildren(final boolean hasChildren) {
259            mHasChildren = hasChildren;
260            return this;
261        }
262        public Builder setSyncWindow(final int syncWindow) {
263            mSyncWindow = syncWindow;
264            return this;
265        }
266        public Builder setConversationListUri(final Uri conversationListUri) {
267            mConversationListUri = conversationListUri;
268            return this;
269        }
270        public Builder setChildFoldersListUri(final Uri childFoldersListUri) {
271            mChildFoldersListUri = childFoldersListUri;
272            return this;
273        }
274        public Builder setUnseenCount(final int unseenCount) {
275            mUnseenCount = unseenCount;
276            return this;
277        }
278        public Builder setUnreadCount(final int unreadCount) {
279            mUnreadCount = unreadCount;
280            return this;
281        }
282        public Builder setTotalCount(final int totalCount) {
283            mTotalCount = totalCount;
284            return this;
285        }
286        public Builder setRefreshUri(final Uri refreshUri) {
287            mRefreshUri = refreshUri;
288            return this;
289        }
290        public Builder setSyncStatus(final int syncStatus) {
291            mSyncStatus = syncStatus;
292            return this;
293        }
294        public Builder setLastSyncResult(final int lastSyncResult) {
295            mLastSyncResult = lastSyncResult;
296            return this;
297        }
298        public Builder setType(final int type) {
299            mType = type;
300            return this;
301        }
302        public Builder setIconResId(final int iconResId) {
303            mIconResId = iconResId;
304            return this;
305        }
306        public Builder setNotificationIconResId(final int notificationIconResId) {
307            mNotificationIconResId = notificationIconResId;
308            return this;
309        }
310        public Builder setBgColor(final String bgColor) {
311            mBgColor = bgColor;
312            return this;
313        }
314        public Builder setFgColor(final String fgColor) {
315            mFgColor = fgColor;
316            return this;
317        }
318        public Builder setLoadMoreUri(final Uri loadMoreUri) {
319            mLoadMoreUri = loadMoreUri;
320            return this;
321        }
322        public Builder setHierarchicalDesc(final String hierarchicalDesc) {
323            mHierarchicalDesc = hierarchicalDesc;
324            return this;
325        }
326        public Builder setParent(final Uri parent) {
327            mParent = parent;
328            return this;
329        }
330        public Builder setLastMessageTimestamp(final long lastMessageTimestamp) {
331            mLastMessageTimestamp = lastMessageTimestamp;
332            return this;
333        }
334        public Builder setUnreadSenders(final String unreadSenders) {
335            mUnreadSenders = unreadSenders;
336            return this;
337        }
338    }
339
340    public Folder(int id, String persistentId, Uri uri, String name, int capabilities,
341            boolean hasChildren, int syncWindow, Uri conversationListUri, Uri childFoldersListUri,
342            int unseenCount, int unreadCount, int totalCount, Uri refreshUri, int syncStatus,
343            int lastSyncResult, int type, int iconResId, int notificationIconResId, String bgColor,
344            String fgColor, Uri loadMoreUri, String hierarchicalDesc, Uri parent,
345            final long lastMessageTimestamp, final String unreadSenders) {
346        this.id = id;
347        this.persistentId = persistentId;
348        this.folderUri = new FolderUri(uri);
349        this.name = name;
350        this.capabilities = capabilities;
351        this.hasChildren = hasChildren;
352        this.syncWindow = syncWindow;
353        this.conversationListUri = conversationListUri;
354        this.childFoldersListUri = childFoldersListUri;
355        this.unseenCount = unseenCount;
356        this.unreadCount = unreadCount;
357        this.totalCount = totalCount;
358        this.refreshUri = refreshUri;
359        this.syncStatus = syncStatus;
360        this.lastSyncResult = lastSyncResult;
361        this.type = type;
362        this.iconResId = iconResId;
363        this.bgColor = bgColor;
364        this.fgColor = fgColor;
365        if (!TextUtils.isEmpty(bgColor)) {
366            this.bgColorInt = Integer.parseInt(bgColor);
367        }
368        if (!TextUtils.isEmpty(fgColor)) {
369            this.fgColorInt = Integer.parseInt(fgColor);
370        }
371        this.loadMoreUri = loadMoreUri;
372        this.hierarchicalDesc = hierarchicalDesc;
373        this.lastMessageTimestamp = lastMessageTimestamp;
374        this.parent = parent;
375        this.unreadSenders = unreadSenders;
376    }
377
378    public Folder(Cursor cursor) {
379        id = cursor.getInt(UIProvider.FOLDER_ID_COLUMN);
380        persistentId = cursor.getString(UIProvider.FOLDER_PERSISTENT_ID_COLUMN);
381        folderUri =
382                new FolderUri(Uri.parse(cursor.getString(UIProvider.FOLDER_URI_COLUMN)));
383        name = cursor.getString(UIProvider.FOLDER_NAME_COLUMN);
384        capabilities = cursor.getInt(UIProvider.FOLDER_CAPABILITIES_COLUMN);
385        // 1 for true, 0 for false.
386        hasChildren = cursor.getInt(UIProvider.FOLDER_HAS_CHILDREN_COLUMN) == 1;
387        syncWindow = cursor.getInt(UIProvider.FOLDER_SYNC_WINDOW_COLUMN);
388        String convList = cursor.getString(UIProvider.FOLDER_CONVERSATION_LIST_URI_COLUMN);
389        conversationListUri = !TextUtils.isEmpty(convList) ? Uri.parse(convList) : null;
390        String childList = cursor.getString(UIProvider.FOLDER_CHILD_FOLDERS_LIST_COLUMN);
391        childFoldersListUri = (hasChildren && !TextUtils.isEmpty(childList)) ? Uri.parse(childList)
392                : null;
393        unseenCount = cursor.getInt(UIProvider.FOLDER_UNSEEN_COUNT_COLUMN);
394        unreadCount = cursor.getInt(UIProvider.FOLDER_UNREAD_COUNT_COLUMN);
395        totalCount = cursor.getInt(UIProvider.FOLDER_TOTAL_COUNT_COLUMN);
396        String refresh = cursor.getString(UIProvider.FOLDER_REFRESH_URI_COLUMN);
397        refreshUri = !TextUtils.isEmpty(refresh) ? Uri.parse(refresh) : null;
398        syncStatus = cursor.getInt(UIProvider.FOLDER_SYNC_STATUS_COLUMN);
399        lastSyncResult = cursor.getInt(UIProvider.FOLDER_LAST_SYNC_RESULT_COLUMN);
400        type = cursor.getInt(UIProvider.FOLDER_TYPE_COLUMN);
401        iconResId = cursor.getInt(UIProvider.FOLDER_ICON_RES_ID_COLUMN);
402        bgColor = cursor.getString(UIProvider.FOLDER_BG_COLOR_COLUMN);
403        fgColor = cursor.getString(UIProvider.FOLDER_FG_COLOR_COLUMN);
404        if (!TextUtils.isEmpty(bgColor)) {
405            bgColorInt = Integer.parseInt(bgColor);
406        }
407        if (!TextUtils.isEmpty(fgColor)) {
408            fgColorInt = Integer.parseInt(fgColor);
409        }
410        String loadMore = cursor.getString(UIProvider.FOLDER_LOAD_MORE_URI_COLUMN);
411        loadMoreUri = !TextUtils.isEmpty(loadMore) ? Uri.parse(loadMore) : null;
412        hierarchicalDesc = cursor.getString(UIProvider.FOLDER_HIERARCHICAL_DESC_COLUMN);
413        lastMessageTimestamp = cursor.getLong(UIProvider.FOLDER_LAST_MESSAGE_TIMESTAMP_COLUMN);
414        // A null parent URI means that this is a top-level folder.
415        final String parentString = cursor.getString(UIProvider.FOLDER_PARENT_URI_COLUMN);
416        parent = parentString == null ? Uri.EMPTY : Uri.parse(parentString);
417        final int unreadSendersColumn =
418                cursor.getColumnIndex(UIProvider.FolderColumns.UNREAD_SENDERS);
419        if (unreadSendersColumn != -1) {
420            unreadSenders = cursor.getString(unreadSendersColumn);
421        } else {
422            unreadSenders = null;
423        }
424    }
425
426    /**
427     * Public object that knows how to construct Folders given Cursors.
428     */
429    public static final CursorCreator<Folder> FACTORY = new CursorCreator<Folder>() {
430        @Override
431        public Folder createFromCursor(Cursor c) {
432            return new Folder(c);
433        }
434
435        @Override
436        public String toString() {
437            return "Folder CursorCreator";
438        }
439    };
440
441    public Folder(Parcel in, ClassLoader loader) {
442        id = in.readInt();
443        persistentId = in.readString();
444        folderUri = new FolderUri((Uri) in.readParcelable(loader));
445        name = in.readString();
446        capabilities = in.readInt();
447        // 1 for true, 0 for false.
448        hasChildren = in.readInt() == 1;
449        syncWindow = in.readInt();
450        conversationListUri = in.readParcelable(loader);
451        childFoldersListUri = in.readParcelable(loader);
452        unseenCount = in.readInt();
453        unreadCount = in.readInt();
454        totalCount = in.readInt();
455        refreshUri = in.readParcelable(loader);
456        syncStatus = in.readInt();
457        lastSyncResult = in.readInt();
458        type = in.readInt();
459        iconResId = in.readInt();
460        bgColor = in.readString();
461        fgColor = in.readString();
462        if (!TextUtils.isEmpty(bgColor)) {
463            bgColorInt = Integer.parseInt(bgColor);
464        }
465        if (!TextUtils.isEmpty(fgColor)) {
466            fgColorInt = Integer.parseInt(fgColor);
467        }
468        loadMoreUri = in.readParcelable(loader);
469        hierarchicalDesc = in.readString();
470        parent = in.readParcelable(loader);
471        lastMessageTimestamp = in.readLong();
472        parent = in.readParcelable(loader);
473        unreadSenders = in.readString();
474     }
475
476    @Override
477    public void writeToParcel(Parcel dest, int flags) {
478        dest.writeInt(id);
479        dest.writeString(persistentId);
480        dest.writeParcelable(folderUri != null ? folderUri.fullUri : null, 0);
481        dest.writeString(name);
482        dest.writeInt(capabilities);
483        // 1 for true, 0 for false.
484        dest.writeInt(hasChildren ? 1 : 0);
485        dest.writeInt(syncWindow);
486        dest.writeParcelable(conversationListUri, 0);
487        dest.writeParcelable(childFoldersListUri, 0);
488        dest.writeInt(unseenCount);
489        dest.writeInt(unreadCount);
490        dest.writeInt(totalCount);
491        dest.writeParcelable(refreshUri, 0);
492        dest.writeInt(syncStatus);
493        dest.writeInt(lastSyncResult);
494        dest.writeInt(type);
495        dest.writeInt(iconResId);
496        dest.writeString(bgColor);
497        dest.writeString(fgColor);
498        dest.writeParcelable(loadMoreUri, 0);
499        dest.writeString(hierarchicalDesc);
500        dest.writeParcelable(parent, 0);
501        dest.writeLong(lastMessageTimestamp);
502        dest.writeParcelable(parent, 0);
503        dest.writeString(unreadSenders);
504    }
505
506    /**
507     * Construct a folder that queries for search results. Do not call on the UI
508     * thread.
509     */
510    public static ObjectCursorLoader<Folder> forSearchResults(Account account, String query,
511            String queryIdentifier, Context context) {
512        if (account.searchUri != null) {
513            final Uri.Builder searchBuilder = account.searchUri.buildUpon();
514            searchBuilder.appendQueryParameter(UIProvider.SearchQueryParameters.QUERY, query);
515            searchBuilder.appendQueryParameter(UIProvider.SearchQueryParameters.QUERY_IDENTIFER,
516                    queryIdentifier);
517            final Uri searchUri = searchBuilder.build();
518            return new ObjectCursorLoader<Folder>(context, searchUri, UIProvider.FOLDERS_PROJECTION,
519                    FACTORY);
520        }
521        return null;
522    }
523
524    public static HashMap<Uri, Folder> hashMapForFolders(List<Folder> rawFolders) {
525        final HashMap<Uri, Folder> folders = new HashMap<Uri, Folder>();
526        for (Folder f : rawFolders) {
527            folders.put(f.folderUri.getComparisonUri(), f);
528        }
529        return folders;
530    }
531
532    /**
533     * Constructor that leaves everything uninitialized.
534     */
535    private Folder() {
536        name = FOLDER_UNINITIALIZED;
537    }
538
539    /**
540     * Creates a new instance of a folder object that is <b>not</b> initialized.  The caller is
541     * expected to fill in the details. Used only for testing.
542     * @return a new instance of an unsafe folder.
543     */
544    @VisibleForTesting
545    public static Folder newUnsafeInstance() {
546        return new Folder();
547    }
548
549    public static final ClassLoaderCreator<Folder> CREATOR = new ClassLoaderCreator<Folder>() {
550        @Override
551        public Folder createFromParcel(Parcel source) {
552            return new Folder(source, null);
553        }
554
555        @Override
556        public Folder createFromParcel(Parcel source, ClassLoader loader) {
557            return new Folder(source, loader);
558        }
559
560        @Override
561        public Folder[] newArray(int size) {
562            return new Folder[size];
563        }
564    };
565
566    @Override
567    public int describeContents() {
568        // Return a sort of version number for this parcelable folder. Starting with zero.
569        return 0;
570    }
571
572    @Override
573    public boolean equals(Object o) {
574        if (o == null || !(o instanceof Folder)) {
575            return false;
576        }
577        return Objects.equal(folderUri, ((Folder) o).folderUri);
578    }
579
580    @Override
581    public int hashCode() {
582        return folderUri == null ? 0 : folderUri.hashCode();
583    }
584
585    @Override
586    public String toString() {
587        // log extra info at DEBUG level or finer
588        final StringBuilder sb = new StringBuilder(super.toString());
589        sb.append("{id=");
590        sb.append(id);
591        if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
592            sb.append(", uri=");
593            sb.append(folderUri);
594            sb.append(", name=");
595            sb.append(name);
596            sb.append(", count=");
597            sb.append(totalCount);
598        }
599        sb.append("}");
600        return sb.toString();
601    }
602
603    @Override
604    public int compareTo(Folder other) {
605        return name.compareToIgnoreCase(other.name);
606    }
607
608    /**
609     * Returns a boolean indicating whether network activity (sync) is occuring for this folder.
610     */
611    public boolean isSyncInProgress() {
612        return UIProvider.SyncStatus.isSyncInProgress(syncStatus);
613    }
614
615    public boolean supportsCapability(int capability) {
616        return (capabilities & capability) != 0;
617    }
618
619    // Show black text on a transparent swatch for system folders, effectively hiding the
620    // swatch (see bug 2431925).
621    public static void setFolderBlockColor(Folder folder, View colorBlock) {
622        if (colorBlock == null) {
623            return;
624        }
625        boolean showBg =
626                !TextUtils.isEmpty(folder.bgColor) && (folder.type & FolderType.INBOX_SECTION) == 0;
627        final int backgroundColor = showBg ? Integer.parseInt(folder.bgColor) : 0;
628        if (backgroundColor == Utils.getDefaultFolderBackgroundColor(colorBlock.getContext())) {
629            showBg = false;
630        }
631        if (!showBg) {
632            colorBlock.setBackgroundDrawable(null);
633            colorBlock.setVisibility(View.GONE);
634        } else {
635            PaintDrawable paintDrawable = new PaintDrawable();
636            paintDrawable.getPaint().setColor(backgroundColor);
637            colorBlock.setBackgroundDrawable(paintDrawable);
638            colorBlock.setVisibility(View.VISIBLE);
639        }
640    }
641
642    private static final int[] ACTIVATED_STATE_LIST = new int[] {android.R.attr.state_activated};
643
644    public static void setIcon(Folder folder, ImageView iconView) {
645        if (iconView == null) {
646            return;
647        }
648        int icon = folder.iconResId;
649
650        // If we're using the default folders, make sure we show the parent icon
651        if (icon == R.drawable.ic_drawer_folder_24dp && folder.hasChildren) {
652            icon = R.drawable.ic_folder_parent_24dp;
653        }
654
655        if (icon > 0) {
656            final Drawable defaultIconDrawable = iconView.getResources().getDrawable(icon);
657            if (defaultIconDrawable != null) {
658                final Drawable iconDrawable;
659                if (folder.supportsCapability(UIProvider.FolderCapabilities.TINT_ICON)) {
660                    // Default multiply by white
661                    defaultIconDrawable.mutate().setColorFilter(folder.getBackgroundColor(0xFFFFFF),
662                            PorterDuff.Mode.MULTIPLY);
663                    iconDrawable = defaultIconDrawable;
664                } else {
665                    final StateListDrawable listDrawable = new StateListDrawable();
666
667                    final Drawable activatedIconDrawable =
668                            iconView.getResources().getDrawable(icon);
669                    activatedIconDrawable.mutate().setColorFilter(0xff000000,
670                            PorterDuff.Mode.MULTIPLY);
671
672                    listDrawable.addState(ACTIVATED_STATE_LIST, activatedIconDrawable);
673                    listDrawable.addState(StateSet.WILD_CARD, defaultIconDrawable);
674
675                    iconDrawable = listDrawable;
676                }
677                iconView.setImageDrawable(iconDrawable);
678            } else {
679                iconView.setImageDrawable(null);
680            }
681        } else {
682            LogUtils.e(LogUtils.TAG, "No icon returned for folder %s", folder);
683        }
684    }
685
686    /**
687     * Return if the type of the folder matches a provider defined folder.
688     */
689    public boolean isProviderFolder() {
690        return !isType(UIProvider.FolderType.DEFAULT);
691    }
692
693    public int getBackgroundColor(int defaultColor) {
694        return !TextUtils.isEmpty(bgColor) ? bgColorInt : defaultColor;
695    }
696
697    public int getForegroundColor(int defaultColor) {
698        return !TextUtils.isEmpty(fgColor) ? fgColorInt : defaultColor;
699    }
700
701    /**
702     * Get just the uri's from an arraylist of folders.
703     */
704    public static String[] getUriArray(List<Folder> folders) {
705        if (folders == null || folders.size() == 0) {
706            return new String[0];
707        }
708        final String[] folderUris = new String[folders.size()];
709        int i = 0;
710        for (Folder folder : folders) {
711            folderUris[i] = folder.folderUri.toString();
712            i++;
713        }
714        return folderUris;
715    }
716
717    /**
718     * Returns a boolean indicating whether this Folder object has been initialized
719     */
720    public boolean isInitialized() {
721        return !name.equals(FOLDER_UNINITIALIZED) && conversationListUri != null &&
722                !NULL_STRING_URI.equals(conversationListUri.toString());
723    }
724
725    public boolean isType(final int folderType) {
726        return isType(type, folderType);
727    }
728
729    /**
730     * Checks if <code>typeMask</code> is of the specified {@link FolderType}
731     *
732     * @return <code>true</code> if the mask contains the specified
733     *         {@link FolderType}, <code>false</code> otherwise
734     */
735    public static boolean isType(final int typeMask, final int folderType) {
736        return (typeMask & folderType) != 0;
737    }
738
739    /**
740     * Returns {@code true} if this folder is an inbox folder.
741     */
742    public boolean isInbox() {
743        return isType(FolderType.INBOX);
744    }
745
746    /**
747     * Returns {@code true} if this folder is a search folder.
748     */
749    public boolean isSearch() {
750        return isType(FolderType.SEARCH);
751    }
752
753    /**
754     * Returns {@code true} if this folder is the spam folder.
755     */
756    public boolean isSpam() {
757        return isType(FolderType.SPAM);
758    }
759
760    /**
761     * Return if this is the trash folder.
762     */
763    public boolean isTrash() {
764        return isType(FolderType.TRASH);
765    }
766
767    /**
768     * Return if this is a draft folder.
769     */
770    public boolean isDraft() {
771        return isType(FolderType.DRAFT);
772    }
773
774    /**
775     * Whether this folder supports only showing important messages.
776     */
777    public boolean isImportantOnly() {
778        return supportsCapability(
779                UIProvider.FolderCapabilities.ONLY_IMPORTANT);
780    }
781
782    /**
783     * Return if this is the sent folder.
784     */
785    public boolean isSent() {
786        return isType(FolderType.SENT);
787    }
788
789    /**
790     * Return if this is the outbox folder
791     */
792    public boolean isOutbox() {
793        return isType(FolderType.OUTBOX);
794    }
795
796    /**
797     * Whether this is the special folder just used to display all mail for an account.
798     */
799    public boolean isViewAll() {
800        return isType(FolderType.ALL_MAIL);
801    }
802
803    /**
804     * Return true if this folder prefers to display recipients over senders.
805     */
806    public boolean shouldShowRecipients() {
807        return supportsCapability(UIProvider.FolderCapabilities.SHOW_RECIPIENTS);
808    }
809
810    /**
811     * Return true if this folder prefers to display recipients over senders.
812     */
813    public static boolean shouldShowRecipients(final int folderCapabilities) {
814        return (folderCapabilities & UIProvider.FolderCapabilities.SHOW_RECIPIENTS) != 0;
815    }
816
817    /**
818     * @return a non-user facing English string describing this folder's type
819     */
820    public String getTypeDescription() {
821        final String desc;
822        if (isType(FolderType.INBOX_SECTION)) {
823            desc = "inbox_section:" + persistentId;
824        } else if (isInbox()) {
825            desc = "inbox:" + persistentId;
826        } else if (isDraft()) {
827            desc = "draft";
828        } else if (isImportantOnly()) {
829            desc = "important";
830        } else if (isType(FolderType.OUTBOX)) {
831            desc = "outbox";
832        } else if (isType(FolderType.SENT)) {
833            desc = "sent";
834        } else if (isType(FolderType.SPAM)) {
835            desc = "spam";
836        } else if (isType(FolderType.STARRED)) {
837            desc = "starred";
838        } else if (isTrash()) {
839            desc = "trash";
840        } else if (isType(FolderType.UNREAD)) {
841            desc = "unread";
842        } else if (isType(FolderType.SEARCH)) {
843            desc = "search";
844        } else if (isViewAll()) {
845            desc = "all_mail";
846        } else if (isProviderFolder()) {
847            desc = "other:" + persistentId;
848        } else {
849            desc = "user_folder";
850        }
851        return desc;
852    }
853
854    /**
855     * True if the previous sync was successful, false otherwise.
856     * @return
857     */
858    public final boolean wasSyncSuccessful() {
859        return ((lastSyncResult & 0x0f) == UIProvider.LastSyncResult.SUCCESS);
860    }
861
862    /**
863     * Returns true if unread count should be suppressed for this folder. This is done for folders
864     * where the unread count is meaningless: trash or drafts, for instance.
865     * @return true if unread count should be suppressed for this object.
866     */
867    public final boolean isUnreadCountHidden() {
868        return (isDraft() || isTrash() || isType(FolderType.OUTBOX));
869    }
870
871    /**
872     * This method is only used for parsing folders out of legacy intent extras, and only the
873     * folderUri and conversationListUri fields are actually read before the object is discarded.
874     * TODO: replace this with a parsing function that just directly returns those values
875     * @param inString UR8 or earlier EXTRA_FOLDER intent extra string
876     * @return Constructed folder object
877     */
878    @Deprecated
879    public static Folder fromString(String inString) {
880        if (TextUtils.isEmpty(inString)) {
881            return null;
882        }
883        final Folder f = new Folder();
884        int indexOf = inString.indexOf(SPLITTER);
885        int id = -1;
886        if (indexOf != -1) {
887            id = Integer.valueOf(inString.substring(0, indexOf));
888        } else {
889            // If no separator was found, we can't parse this folder and the
890            // TextUtils.split call would also fail. Return null.
891            return null;
892        }
893        final String[] split = TextUtils.split(inString, SPLITTER_REGEX);
894        if (split.length < 20) {
895            LogUtils.e(LOG_TAG, "split.length %d", split.length);
896            return null;
897        }
898        f.id = id;
899        int index = 1;
900        f.folderUri = new FolderUri(Folder.getValidUri(split[index++]));
901        f.name = split[index++];
902        f.hasChildren = Integer.parseInt(split[index++]) != 0;
903        f.capabilities = Integer.parseInt(split[index++]);
904        f.syncWindow = Integer.parseInt(split[index++]);
905        f.conversationListUri = getValidUri(split[index++]);
906        f.childFoldersListUri = getValidUri(split[index++]);
907        f.unreadCount = Integer.parseInt(split[index++]);
908        f.totalCount = Integer.parseInt(split[index++]);
909        f.refreshUri = getValidUri(split[index++]);
910        f.syncStatus = Integer.parseInt(split[index++]);
911        f.lastSyncResult = Integer.parseInt(split[index++]);
912        f.type = Integer.parseInt(split[index++]);
913        f.iconResId = Integer.parseInt(split[index++]);
914        f.bgColor = split[index++];
915        f.fgColor = split[index++];
916        if (!TextUtils.isEmpty(f.bgColor)) {
917            f.bgColorInt = Integer.parseInt(f.bgColor);
918        }
919        if (!TextUtils.isEmpty(f.fgColor)) {
920            f.fgColorInt = Integer.parseInt(f.fgColor);
921        }
922        f.loadMoreUri = getValidUri(split[index++]);
923        f.hierarchicalDesc = split[index++];
924        f.parent = Folder.getValidUri(split[index++]);
925        f.unreadSenders = null;
926
927        return f;
928    }
929
930    private static Uri getValidUri(String uri) {
931        if (TextUtils.isEmpty(uri)) {
932            return null;
933        }
934        return Uri.parse(uri);
935    }
936
937    public static final boolean isRoot(Folder folder) {
938        return (folder == null) || Uri.EMPTY.equals(folder.parent);
939    }
940}
941