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