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 
18 package com.android.mail.providers;
19 
20 import android.content.Context;
21 import android.database.Cursor;
22 import android.graphics.PorterDuff;
23 import android.graphics.drawable.Drawable;
24 import android.graphics.drawable.PaintDrawable;
25 import android.graphics.drawable.StateListDrawable;
26 import android.net.Uri;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.text.TextUtils;
30 import android.util.StateSet;
31 import android.view.View;
32 import android.widget.ImageView;
33 
34 import com.android.mail.R;
35 import com.android.mail.content.CursorCreator;
36 import com.android.mail.content.ObjectCursorLoader;
37 import com.android.mail.providers.UIProvider.FolderType;
38 import com.android.mail.utils.FolderUri;
39 import com.android.mail.utils.LogTag;
40 import com.android.mail.utils.LogUtils;
41 import com.android.mail.utils.Utils;
42 import com.google.common.annotations.VisibleForTesting;
43 import com.google.common.base.Objects;
44 
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.HashMap;
48 import java.util.List;
49 import 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
55 public 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 
build()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 
setId(final int id)238         public Builder setId(final int id) {
239             mId = id;
240             return this;
241         }
setPersistentId(final String persistentId)242         public Builder setPersistentId(final String persistentId) {
243             mPersistentId = persistentId;
244             return this;
245         }
setUri(final Uri uri)246         public Builder setUri(final Uri uri) {
247             mUri = uri;
248             return this;
249         }
setName(final String name)250         public Builder setName(final String name) {
251             mName = name;
252             return this;
253         }
setCapabilities(final int capabilities)254         public Builder setCapabilities(final int capabilities) {
255             mCapabilities = capabilities;
256             return this;
257         }
setHasChildren(final boolean hasChildren)258         public Builder setHasChildren(final boolean hasChildren) {
259             mHasChildren = hasChildren;
260             return this;
261         }
setSyncWindow(final int syncWindow)262         public Builder setSyncWindow(final int syncWindow) {
263             mSyncWindow = syncWindow;
264             return this;
265         }
setConversationListUri(final Uri conversationListUri)266         public Builder setConversationListUri(final Uri conversationListUri) {
267             mConversationListUri = conversationListUri;
268             return this;
269         }
setChildFoldersListUri(final Uri childFoldersListUri)270         public Builder setChildFoldersListUri(final Uri childFoldersListUri) {
271             mChildFoldersListUri = childFoldersListUri;
272             return this;
273         }
setUnseenCount(final int unseenCount)274         public Builder setUnseenCount(final int unseenCount) {
275             mUnseenCount = unseenCount;
276             return this;
277         }
setUnreadCount(final int unreadCount)278         public Builder setUnreadCount(final int unreadCount) {
279             mUnreadCount = unreadCount;
280             return this;
281         }
setTotalCount(final int totalCount)282         public Builder setTotalCount(final int totalCount) {
283             mTotalCount = totalCount;
284             return this;
285         }
setRefreshUri(final Uri refreshUri)286         public Builder setRefreshUri(final Uri refreshUri) {
287             mRefreshUri = refreshUri;
288             return this;
289         }
setSyncStatus(final int syncStatus)290         public Builder setSyncStatus(final int syncStatus) {
291             mSyncStatus = syncStatus;
292             return this;
293         }
setLastSyncResult(final int lastSyncResult)294         public Builder setLastSyncResult(final int lastSyncResult) {
295             mLastSyncResult = lastSyncResult;
296             return this;
297         }
setType(final int type)298         public Builder setType(final int type) {
299             mType = type;
300             return this;
301         }
setIconResId(final int iconResId)302         public Builder setIconResId(final int iconResId) {
303             mIconResId = iconResId;
304             return this;
305         }
setNotificationIconResId(final int notificationIconResId)306         public Builder setNotificationIconResId(final int notificationIconResId) {
307             mNotificationIconResId = notificationIconResId;
308             return this;
309         }
setBgColor(final String bgColor)310         public Builder setBgColor(final String bgColor) {
311             mBgColor = bgColor;
312             return this;
313         }
setFgColor(final String fgColor)314         public Builder setFgColor(final String fgColor) {
315             mFgColor = fgColor;
316             return this;
317         }
setLoadMoreUri(final Uri loadMoreUri)318         public Builder setLoadMoreUri(final Uri loadMoreUri) {
319             mLoadMoreUri = loadMoreUri;
320             return this;
321         }
setHierarchicalDesc(final String hierarchicalDesc)322         public Builder setHierarchicalDesc(final String hierarchicalDesc) {
323             mHierarchicalDesc = hierarchicalDesc;
324             return this;
325         }
setParent(final Uri parent)326         public Builder setParent(final Uri parent) {
327             mParent = parent;
328             return this;
329         }
setLastMessageTimestamp(final long lastMessageTimestamp)330         public Builder setLastMessageTimestamp(final long lastMessageTimestamp) {
331             mLastMessageTimestamp = lastMessageTimestamp;
332             return this;
333         }
setUnreadSenders(final String unreadSenders)334         public Builder setUnreadSenders(final String unreadSenders) {
335             mUnreadSenders = unreadSenders;
336             return this;
337         }
338     }
339 
Folder(int id, String persistentId, Uri uri, String name, int capabilities, boolean hasChildren, int syncWindow, Uri conversationListUri, Uri childFoldersListUri, int unseenCount, int unreadCount, int totalCount, Uri refreshUri, int syncStatus, int lastSyncResult, int type, int iconResId, int notificationIconResId, String bgColor, String fgColor, Uri loadMoreUri, String hierarchicalDesc, Uri parent, final long lastMessageTimestamp, final String unreadSenders)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 
Folder(Cursor cursor)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 
Folder(Parcel in, ClassLoader loader)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
writeToParcel(Parcel dest, int flags)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      */
forSearchResults(Account account, String query, String queryIdentifier, Context context)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 
hashMapForFolders(List<Folder> rawFolders)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      */
Folder()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
newUnsafeInstance()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
describeContents()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
equals(Object o)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
hashCode()581     public int hashCode() {
582         return folderUri == null ? 0 : folderUri.hashCode();
583     }
584 
585     @Override
toString()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
compareTo(Folder other)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      */
isSyncInProgress()611     public boolean isSyncInProgress() {
612         return UIProvider.SyncStatus.isSyncInProgress(syncStatus);
613     }
614 
supportsCapability(int capability)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).
setFolderBlockColor(Folder folder, View colorBlock)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 
setIcon(Folder folder, ImageView iconView)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      */
isProviderFolder()689     public boolean isProviderFolder() {
690         return !isType(UIProvider.FolderType.DEFAULT);
691     }
692 
getBackgroundColor(int defaultColor)693     public int getBackgroundColor(int defaultColor) {
694         return !TextUtils.isEmpty(bgColor) ? bgColorInt : defaultColor;
695     }
696 
getForegroundColor(int defaultColor)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      */
getUriArray(List<Folder> folders)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      */
isInitialized()720     public boolean isInitialized() {
721         return !name.equals(FOLDER_UNINITIALIZED) && conversationListUri != null &&
722                 !NULL_STRING_URI.equals(conversationListUri.toString());
723     }
724 
isType(final int folderType)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      */
isType(final int typeMask, final int folderType)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      */
isInbox()742     public boolean isInbox() {
743         return isType(FolderType.INBOX);
744     }
745 
746     /**
747      * Returns {@code true} if this folder is a search folder.
748      */
isSearch()749     public boolean isSearch() {
750         return isType(FolderType.SEARCH);
751     }
752 
753     /**
754      * Returns {@code true} if this folder is the spam folder.
755      */
isSpam()756     public boolean isSpam() {
757         return isType(FolderType.SPAM);
758     }
759 
760     /**
761      * Return if this is the trash folder.
762      */
isTrash()763     public boolean isTrash() {
764         return isType(FolderType.TRASH);
765     }
766 
767     /**
768      * Return if this is a draft folder.
769      */
isDraft()770     public boolean isDraft() {
771         return isType(FolderType.DRAFT);
772     }
773 
774     /**
775      * Whether this folder supports only showing important messages.
776      */
isImportantOnly()777     public boolean isImportantOnly() {
778         return supportsCapability(
779                 UIProvider.FolderCapabilities.ONLY_IMPORTANT);
780     }
781 
782     /**
783      * Return if this is the sent folder.
784      */
isSent()785     public boolean isSent() {
786         return isType(FolderType.SENT);
787     }
788 
789     /**
790      * Return if this is the outbox folder
791      */
isOutbox()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      */
isViewAll()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      */
shouldShowRecipients()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      */
shouldShowRecipients(final int folderCapabilities)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      */
getTypeDescription()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      */
wasSyncSuccessful()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      */
isUnreadCountHidden()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
fromString(String inString)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 
getValidUri(String uri)930     private static Uri getValidUri(String uri) {
931         if (TextUtils.isEmpty(uri)) {
932             return null;
933         }
934         return Uri.parse(uri);
935     }
936 
isRoot(Folder folder)937     public static final boolean isRoot(Folder folder) {
938         return (folder == null) || Uri.EMPTY.equals(folder.parent);
939     }
940 }
941