Folder.java revision bc67bb1211654e69b1233e0f8759771873ca773e
1/******************************************************************************* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 *******************************************************************************/ 17 18package com.android.mail.providers; 19 20import android.content.Context; 21import android.content.CursorLoader; 22import android.database.Cursor; 23import android.graphics.Color; 24import android.graphics.drawable.PaintDrawable; 25import android.net.Uri; 26import android.net.Uri.Builder; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.os.Parcelable.Creator; 30import android.text.TextUtils; 31import android.view.View; 32 33import com.android.mail.utils.LogUtils; 34import com.google.common.collect.Lists; 35import com.google.common.collect.Maps; 36 37import java.util.ArrayList; 38import java.util.Collection; 39import java.util.List; 40import java.util.Map; 41import java.util.regex.Pattern; 42 43/** 44 * A folder is a collection of conversations, and perhaps other folders. 45 */ 46public class Folder implements Parcelable, Comparable<Folder> { 47 /** 48 * 49 */ 50 private static final String FOLDER_UNINITIALIZED = "Uninitialized!"; 51 52 // Try to match the order of members with the order of constants in UIProvider. 53 54 /** 55 * Unique id of this folder. 56 */ 57 public int id; 58 59 /** 60 * The content provider URI that returns this folder for this account. 61 */ 62 public Uri uri; 63 64 /** 65 * The human visible name for this folder. 66 */ 67 public String name; 68 69 /** 70 * The possible capabilities that this folder supports. 71 */ 72 public int capabilities; 73 74 /** 75 * Whether or not this folder has children folders. 76 */ 77 public boolean hasChildren; 78 79 /** 80 * How large the synchronization window is: how many days worth of data is retained on the 81 * device. 82 */ 83 public int syncWindow; 84 85 /** 86 * The content provider URI to return the list of conversations in this 87 * folder. 88 */ 89 public Uri conversationListUri; 90 91 /** 92 * The content provider URI to return the list of child folders of this folder. 93 */ 94 public Uri childFoldersListUri; 95 96 /** 97 * The number of messages that are unread in this folder. 98 */ 99 public int unreadCount; 100 101 /** 102 * The total number of messages in this folder. 103 */ 104 public int totalCount; 105 106 /** 107 * The content provider URI to force a refresh of this folder. 108 */ 109 public Uri refreshUri; 110 111 /** 112 * The current sync status of the folder 113 */ 114 public int syncStatus; 115 116 /** 117 * The result of the last sync for this folder 118 */ 119 public int lastSyncResult; 120 121 /** 122 * Folder type. 0 is default. 123 */ 124 public int type; 125 126 /** 127 * Icon for this folder; 0 implies no icon. 128 */ 129 public long iconResId; 130 131 public String bgColor; 132 public String fgColor; 133 134 /** 135 * The content provider URI to request additional conversations 136 */ 137 public Uri loadMoreUri; 138 139 /** 140 * Total number of members that comprise an instance of a folder. This is 141 * the number of members that need to be serialized or parceled. 142 */ 143 private static final int NUMBER_MEMBERS = UIProvider.FOLDERS_PROJECTION.length; 144 145 /** 146 * Used only for debugging. 147 */ 148 private static final String LOG_TAG = new LogUtils().getLogTag(); 149 150 /** 151 * Examples of expected format for the joined folder strings 152 * 153 * Example of a joined folder string: 154 * 630107622^*^^i^*^^i^*^0 155 * <id>^*^<canonical name>^*^<name>^*^<color index> 156 * 157 * The sqlite queries will return a list of folder strings separated with "^**^" 158 * Example of a query result: 159 * 630107622^*^^i^*^^i^*^0^**^630107626^*^^u^*^^u^*^0^**^630107627^*^^f^*^^f^*^0 160 */ 161 private static final String FOLDER_COMPONENT_SEPARATOR = "^*^"; 162 private static final Pattern FOLDER_COMPONENT_SEPARATOR_PATTERN = 163 Pattern.compile("\\^\\*\\^"); 164 165 public static final String FOLDER_SEPARATOR = "^**^"; 166 public static final Pattern FOLDER_SEPARATOR_PATTERN = 167 Pattern.compile("\\^\\*\\*\\^"); 168 169 public Folder(Parcel in) { 170 id = in.readInt(); 171 uri = in.readParcelable(null); 172 name = in.readString(); 173 capabilities = in.readInt(); 174 // 1 for true, 0 for false. 175 hasChildren = in.readInt() == 1; 176 syncWindow = in.readInt(); 177 conversationListUri = in.readParcelable(null); 178 childFoldersListUri = in.readParcelable(null); 179 unreadCount = in.readInt(); 180 totalCount = in.readInt(); 181 refreshUri = in.readParcelable(null); 182 syncStatus = in.readInt(); 183 lastSyncResult = in.readInt(); 184 type = in.readInt(); 185 iconResId = in.readLong(); 186 bgColor = in.readString(); 187 fgColor = in.readString(); 188 loadMoreUri = in.readParcelable(null); 189 } 190 191 public Folder(Cursor cursor) { 192 assert (cursor.getColumnCount() == NUMBER_MEMBERS); 193 id = cursor.getInt(UIProvider.FOLDER_ID_COLUMN); 194 uri = Uri.parse(cursor.getString(UIProvider.FOLDER_URI_COLUMN)); 195 name = cursor.getString(UIProvider.FOLDER_NAME_COLUMN); 196 capabilities = cursor.getInt(UIProvider.FOLDER_CAPABILITIES_COLUMN); 197 // 1 for true, 0 for false. 198 hasChildren = cursor.getInt(UIProvider.FOLDER_HAS_CHILDREN_COLUMN) == 1; 199 syncWindow = cursor.getInt(UIProvider.FOLDER_SYNC_WINDOW_COLUMN); 200 String convList = cursor.getString(UIProvider.FOLDER_CONVERSATION_LIST_URI_COLUMN); 201 conversationListUri = !TextUtils.isEmpty(convList) ? Uri.parse(convList) : null; 202 String childList = cursor.getString(UIProvider.FOLDER_CHILD_FOLDERS_LIST_COLUMN); 203 childFoldersListUri = (hasChildren && !TextUtils.isEmpty(childList)) ? Uri.parse(childList) 204 : null; 205 unreadCount = cursor.getInt(UIProvider.FOLDER_UNREAD_COUNT_COLUMN); 206 totalCount = cursor.getInt(UIProvider.FOLDER_TOTAL_COUNT_COLUMN); 207 String refresh = cursor.getString(UIProvider.FOLDER_REFRESH_URI_COLUMN); 208 refreshUri = !TextUtils.isEmpty(refresh) ? Uri.parse(refresh) : null; 209 syncStatus = cursor.getInt(UIProvider.FOLDER_SYNC_STATUS_COLUMN); 210 lastSyncResult = cursor.getInt(UIProvider.FOLDER_LAST_SYNC_RESULT_COLUMN); 211 type = cursor.getInt(UIProvider.FOLDER_TYPE_COLUMN); 212 iconResId = cursor.getLong(UIProvider.FOLDER_ICON_RES_ID_COLUMN); 213 bgColor = cursor.getString(UIProvider.FOLDER_BG_COLOR_COLUMN); 214 fgColor = cursor.getString(UIProvider.FOLDER_FG_COLOR_COLUMN); 215 String loadMore = cursor.getString(UIProvider.FOLDER_LOAD_MORE_URI_COLUMN); 216 loadMoreUri = !TextUtils.isEmpty(loadMore) ? Uri.parse(loadMore) : null; 217 } 218 219 @Override 220 public void writeToParcel(Parcel dest, int flags) { 221 dest.writeInt(id); 222 dest.writeParcelable(uri, 0); 223 dest.writeString(name); 224 dest.writeInt(capabilities); 225 // 1 for true, 0 for false. 226 dest.writeInt(hasChildren ? 1 : 0); 227 dest.writeInt(syncWindow); 228 dest.writeParcelable(conversationListUri, 0); 229 dest.writeParcelable(childFoldersListUri, 0); 230 dest.writeInt(unreadCount); 231 dest.writeInt(totalCount); 232 dest.writeParcelable(refreshUri, 0); 233 dest.writeInt(syncStatus); 234 dest.writeInt(lastSyncResult); 235 dest.writeInt(type); 236 dest.writeLong(iconResId); 237 dest.writeString(bgColor); 238 dest.writeString(fgColor); 239 dest.writeParcelable(loadMoreUri, 0); 240 } 241 242 /** 243 * Return a serialized String for this folder. 244 */ 245 public synchronized String serialize() { 246 StringBuilder out = new StringBuilder(); 247 out.append(id).append(FOLDER_COMPONENT_SEPARATOR); 248 out.append(uri).append(FOLDER_COMPONENT_SEPARATOR); 249 out.append(name).append(FOLDER_COMPONENT_SEPARATOR); 250 out.append(capabilities).append(FOLDER_COMPONENT_SEPARATOR); 251 out.append(hasChildren ? "1": "0").append(FOLDER_COMPONENT_SEPARATOR); 252 out.append(syncWindow).append(FOLDER_COMPONENT_SEPARATOR); 253 out.append(conversationListUri).append(FOLDER_COMPONENT_SEPARATOR); 254 out.append(childFoldersListUri).append(FOLDER_COMPONENT_SEPARATOR); 255 out.append(unreadCount).append(FOLDER_COMPONENT_SEPARATOR); 256 out.append(totalCount).append(FOLDER_COMPONENT_SEPARATOR); 257 out.append(refreshUri).append(FOLDER_COMPONENT_SEPARATOR); 258 out.append(syncStatus).append(FOLDER_COMPONENT_SEPARATOR); 259 out.append(lastSyncResult).append(FOLDER_COMPONENT_SEPARATOR); 260 out.append(type).append(FOLDER_COMPONENT_SEPARATOR); 261 out.append(iconResId).append(FOLDER_COMPONENT_SEPARATOR); 262 out.append(bgColor == null ? "" : bgColor).append(FOLDER_COMPONENT_SEPARATOR); 263 out.append(fgColor == null? "" : fgColor).append(FOLDER_COMPONENT_SEPARATOR); 264 out.append(loadMoreUri); 265 return out.toString(); 266 } 267 268 /** 269 * Construct a folder that queries for search results. Do not call on the UI 270 * thread. 271 */ 272 public static CursorLoader forSearchResults(Account account, String query, Context context) { 273 if (account.searchUri != null) { 274 Builder searchBuilder = account.searchUri.buildUpon(); 275 searchBuilder.appendQueryParameter(UIProvider.SearchQueryParameters.QUERY, query); 276 Uri searchUri = searchBuilder.build(); 277 return new CursorLoader(context, searchUri, UIProvider.FOLDERS_PROJECTION, null, null, 278 null); 279 } 280 return null; 281 } 282 283 public static List<Folder> forFoldersString(String foldersString) { 284 final List<Folder> folders = Lists.newArrayList(); 285 if (foldersString == null) { 286 return folders; 287 } 288 for (String folderStr : TextUtils.split(foldersString, FOLDER_SEPARATOR_PATTERN)) { 289 folders.add(new Folder(folderStr)); 290 } 291 return folders; 292 } 293 294 /** 295 * Construct a new Folder instance from a previously serialized string. 296 * @param serializedFolder string obtained from {@link #serialize()} on a valid folder. 297 */ 298 public Folder(String serializedFolder) { 299 String[] folderMembers = TextUtils.split(serializedFolder, 300 FOLDER_COMPONENT_SEPARATOR_PATTERN); 301 if (folderMembers.length != NUMBER_MEMBERS) { 302 throw new IllegalArgumentException( 303 "Folder de-serializing failed. Wrong number of members detected." 304 + folderMembers.length); 305 } 306 id = Integer.valueOf(folderMembers[0]); 307 uri = Uri.parse(folderMembers[1]); 308 name = folderMembers[2]; 309 capabilities = Integer.valueOf(folderMembers[3]); 310 // 1 for true, 0 for false 311 hasChildren = folderMembers[4] == "1"; 312 syncWindow = Integer.valueOf(folderMembers[5]); 313 String convList = folderMembers[6]; 314 conversationListUri = !TextUtils.isEmpty(convList) ? Uri.parse(convList) : null; 315 String childList = folderMembers[7]; 316 childFoldersListUri = (hasChildren && !TextUtils.isEmpty(childList)) ? Uri.parse(childList) 317 : null; 318 unreadCount = Integer.valueOf(folderMembers[8]); 319 totalCount = Integer.valueOf(folderMembers[9]); 320 String refresh = folderMembers[10]; 321 refreshUri = !TextUtils.isEmpty(refresh) ? Uri.parse(refresh) : null; 322 syncStatus = Integer.valueOf(folderMembers[11]); 323 lastSyncResult = Integer.valueOf(folderMembers[12]); 324 type = Integer.valueOf(folderMembers[13]); 325 iconResId = Long.valueOf(folderMembers[14]); 326 bgColor = folderMembers[15]; 327 fgColor = folderMembers[16]; 328 String loadMore = folderMembers[17]; 329 loadMoreUri = !TextUtils.isEmpty(loadMore) ? Uri.parse(loadMore) : null; 330 } 331 332 /** 333 * Constructor that leaves everything uninitialized. For use only by {@link #serialize()} 334 * which is responsible for filling in all the fields 335 */ 336 public Folder() { 337 name = FOLDER_UNINITIALIZED; 338 } 339 340 @SuppressWarnings("hiding") 341 public static final Creator<Folder> CREATOR = new Creator<Folder>() { 342 @Override 343 public Folder createFromParcel(Parcel source) { 344 return new Folder(source); 345 } 346 347 @Override 348 public Folder[] newArray(int size) { 349 return new Folder[size]; 350 } 351 }; 352 353 @Override 354 public int describeContents() { 355 // Return a sort of version number for this parcelable folder. Starting with zero. 356 return 0; 357 } 358 359 @Override 360 public boolean equals(Object o) { 361 if (o == null || !(o instanceof Folder)) { 362 return false; 363 } 364 final Uri otherUri = ((Folder) o).uri; 365 if (uri == null) { 366 return (otherUri == null); 367 } 368 return uri.equals(otherUri); 369 } 370 371 @Override 372 public int hashCode() { 373 return uri == null ? 0 : uri.hashCode(); 374 } 375 376 @Override 377 public int compareTo(Folder other) { 378 return name.compareToIgnoreCase(other.name); 379 } 380 381 /** 382 * Create a Folder map from a string of serialized folders. This can only be done on the output 383 * of {@link #serialize(Map)}. 384 * @param serializedFolder A string obtained from {@link #serialize(Map)} 385 * @return a Map of folder name to folder. 386 */ 387 public static Map<String, Folder> parseFoldersFromString(String serializedFolder) { 388 LogUtils.d(LOG_TAG, "folder query result: %s", serializedFolder); 389 390 Map<String, Folder> folderMap = Maps.newHashMap(); 391 if (serializedFolder == null || serializedFolder == "") { 392 return folderMap; 393 } 394 String[] folderPieces = TextUtils.split( 395 serializedFolder, FOLDER_COMPONENT_SEPARATOR_PATTERN); 396 for (int i = 0, n = folderPieces.length; i < n; i++) { 397 Folder folder = new Folder(folderPieces[i]); 398 if (folder.name != FOLDER_UNINITIALIZED) { 399 folderMap.put(folder.name, folder); 400 } 401 } 402 return folderMap; 403 } 404 405 /** 406 * Returns a boolean indicating whether network activity (sync) is occuring for this folder. 407 */ 408 public boolean isSyncInProgress() { 409 return 0 != (syncStatus & (UIProvider.SyncStatus.BACKGROUND_SYNC | 410 UIProvider.SyncStatus.USER_REFRESH | 411 UIProvider.SyncStatus.USER_QUERY | 412 UIProvider.SyncStatus.USER_MORE_RESULTS)); 413 } 414 415 /** 416 * Serialize the given list of folders 417 * @param folderMap A valid map of folder names to Folders 418 * @return a string containing a serialized output of folder maps. 419 */ 420 public static String serialize(Map<String, Folder> folderMap) { 421 Collection<Folder> folderCollection = folderMap.values(); 422 Folder[] folderList = folderCollection.toArray(new Folder[]{} ); 423 int numFolders = folderList.length; 424 StringBuilder result = new StringBuilder(); 425 for (int i = 0; i < numFolders; i++) { 426 if (i > 0) { 427 result.append(FOLDER_SEPARATOR); 428 } 429 Folder folder = folderList[i]; 430 result.append(folder.serialize()); 431 } 432 return result.toString(); 433 } 434 435 public boolean supportsCapability(int capability) { 436 return (capabilities & capability) != 0; 437 } 438 439 // Show black text on a transparent swatch for system folders, effectively hiding the 440 // swatch (see bug 2431925). 441 public static void setFolderBlockColor(Folder folder, View colorBlock) { 442 final boolean showBg = !TextUtils.isEmpty(folder.bgColor); 443 final int backgroundColor = showBg ? Integer.parseInt(folder.bgColor) : 0; 444 if (folder.iconResId > 0) { 445 colorBlock.setBackgroundResource((int)folder.iconResId); 446 } else if (!showBg) { 447 colorBlock.setBackgroundDrawable(null); 448 } else { 449 PaintDrawable paintDrawable = new PaintDrawable(); 450 paintDrawable.getPaint().setColor(backgroundColor); 451 colorBlock.setBackgroundDrawable(paintDrawable); 452 } 453 } 454 455 /** 456 * Return if the type of the folder matches a provider defined folder. 457 */ 458 public static boolean isProviderFolder(Folder folder) { 459 int type = folder.type; 460 return type == UIProvider.FolderType.INBOX || 461 type == UIProvider.FolderType.DRAFT || 462 type == UIProvider.FolderType.OUTBOX || 463 type == UIProvider.FolderType.SENT || 464 type == UIProvider.FolderType.TRASH || 465 type == UIProvider.FolderType.SPAM; 466 } 467 468 public int getBackgroundColor(int defaultColor) { 469 return TextUtils.isEmpty(bgColor) ? defaultColor : Integer.parseInt(bgColor); 470 } 471 472 public int getForegroundColor(int defaultColor) { 473 return TextUtils.isEmpty(fgColor) ? defaultColor : Integer.parseInt(fgColor); 474 } 475 476 public static String getSerializedFolderString(Folder currentFolder, 477 Collection<Folder> folders) { 478 final Collection<String> folderList = new ArrayList<String>(); 479 for (Folder folderEntry : folders) { 480 // If the current folder is a system folder, and the folder entry has the same type 481 // as that system defined folder, don't show it. 482 if (!folderEntry.uri.equals(currentFolder.uri) 483 && Folder.isProviderFolder(currentFolder) 484 && folderEntry.type != currentFolder.type) { 485 folderList.add(folderEntry.serialize()); 486 } 487 } 488 return TextUtils.join(Folder.FOLDER_SEPARATOR, folderList); 489 } 490 491 /** 492 * Returns a comma separated list of folder URIs for all the folders in the collection. 493 * @param folders 494 * @return 495 */ 496 public final static String getUriString(Collection<Folder> folders) { 497 final StringBuilder uris = new StringBuilder(); 498 boolean first = true; 499 for (Folder f : folders) { 500 if (first) { 501 first = false; 502 } else { 503 uris.append(','); 504 } 505 uris.append(f.uri.toString()); 506 } 507 return uris.toString(); 508 } 509} 510