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