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