Home | History | Annotate | Download | only in providers
      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 
    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