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