1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.mail.adapter; 18 19 import android.view.LayoutInflater; 20 import android.view.View; 21 import android.view.ViewGroup; 22 import android.widget.TextView; 23 24 import com.android.bitmap.BitmapCache; 25 import com.android.mail.R; 26 import com.android.mail.bitmap.ContactResolver; 27 import com.android.mail.providers.Account; 28 import com.android.mail.providers.Folder; 29 import com.android.mail.ui.AccountItemView; 30 import com.android.mail.ui.ControllableActivity; 31 import com.android.mail.ui.FolderItemView; 32 import com.android.mail.utils.FolderUri; 33 import com.android.mail.utils.LogTag; 34 import com.android.mail.utils.LogUtils; 35 36 37 /** 38 * An element that is shown in the {@link com.android.mail.ui.FolderListFragment}. This class is 39 * only used for elements that are shown in the {@link com.android.mail.ui.DrawerFragment}. 40 * This class is an enumeration of a few element types: Account, a folder, a recent folder, 41 * or a header (a resource string). A {@link DrawerItem} can only be one type and can never 42 * switch types. Items are created using methods like 43 * {@link DrawerItem#ofAccount(ControllableActivity, Account, int, boolean, BitmapCache, 44 * ContactResolver)}, 45 * {@link DrawerItem#ofWaitView(ControllableActivity)}, etc. 46 * 47 * Once created, the item can create a view using 48 * {@link #getView(android.view.View, android.view.ViewGroup)}. 49 */ 50 public class DrawerItem { 51 private static final String LOG_TAG = LogTag.getLogTag(); 52 public final Folder mFolder; 53 public final Account mAccount; 54 private final int mResource; 55 /** True if the drawer item represents the current account, false otherwise */ 56 private final boolean mIsSelected; 57 /** Either {@link #VIEW_ACCOUNT}, {@link #VIEW_FOLDER} or {@link #VIEW_HEADER} */ 58 public final int mType; 59 /** A normal folder, also a child, if a parent is specified. */ 60 public static final int VIEW_FOLDER = 0; 61 /** A text-label which serves as a header in sectioned lists. */ 62 public static final int VIEW_HEADER = 1; 63 /** A text-label which serves as a header in sectioned lists. */ 64 public static final int VIEW_BLANK_HEADER = 2; 65 /** An account object, which allows switching accounts rather than folders. */ 66 public static final int VIEW_ACCOUNT = 3; 67 /** An expandable object for expanding/collapsing more of the list */ 68 public static final int VIEW_WAITING_FOR_SYNC = 4; 69 /** The value (1-indexed) of the last View type. Useful when returning the number of types. */ 70 private static final int LAST_FIELD = VIEW_WAITING_FOR_SYNC + 1; 71 72 /** TODO: On adding another type, be sure to change getViewTypes() */ 73 74 /** The parent activity */ 75 private final ControllableActivity mActivity; 76 private final LayoutInflater mInflater; 77 78 // TODO(viki): Put all these constants in an interface. 79 /** 80 * Either {@link #FOLDER_INBOX}, {@link #FOLDER_RECENT} or {@link #FOLDER_OTHER} when 81 * {@link #mType} is {@link #VIEW_FOLDER}, or an {@link #ACCOUNT} in the case of 82 * accounts, and {@link #INERT_HEADER} otherwise. 83 */ 84 public final int mFolderType; 85 /** Non existent item or folder type not yet set */ 86 public static final int UNSET = 0; 87 /** An unclickable text-header visually separating the different types. */ 88 public static final int INERT_HEADER = 0; 89 /** An inbox folder: Inbox, ...*/ 90 public static final int FOLDER_INBOX = 1; 91 /** A folder from whom a conversation was recently viewed */ 92 public static final int FOLDER_RECENT = 2; 93 /** A non-inbox folder that is shown in the "everything else" group. */ 94 public static final int FOLDER_OTHER = 3; 95 /** An entry for the accounts the user has on the device. */ 96 public static final int ACCOUNT = 4; 97 98 /** True if this view is enabled, false otherwise. */ 99 private final boolean mIsEnabled; 100 101 private BitmapCache mImagesCache; 102 private ContactResolver mContactResolver; 103 104 @Override 105 public String toString() { 106 switch(mType) { 107 case VIEW_FOLDER: 108 return folderToString(); 109 case VIEW_HEADER: 110 return headerToString(); 111 case VIEW_BLANK_HEADER: 112 return blankHeaderToString(); 113 case VIEW_ACCOUNT: 114 return accountToString(); 115 case VIEW_WAITING_FOR_SYNC: 116 return waitToString(); 117 } 118 // Should never come here. 119 return null; 120 } 121 122 /** 123 * Creates a drawer item with every instance variable specified. 124 * 125 * @param type the type of the item. This must be a VIEW_* element 126 * @param activity the underlying activity 127 * @param folder a non-null folder, if this is a folder type 128 * @param folderType the type of the folder. For folders this is: 129 * {@link #FOLDER_INBOX}, {@link #FOLDER_RECENT}, 130 * {@link #FOLDER_OTHER}, or for non-folders this is 131 * {@link #ACCOUNT}, or {@link #INERT_HEADER} 132 * @param account the account object, for an account drawer element 133 * @param resource either the string resource for a header, or the unread 134 * count for an account. 135 * @param isCurrentAccount true if this item is the current account 136 */ 137 private DrawerItem(int type, ControllableActivity activity, Folder folder, int folderType, 138 Account account, int resource, boolean isCurrentAccount, BitmapCache cache, 139 ContactResolver contactResolver) { 140 mActivity = activity; 141 mFolder = folder; 142 mFolderType = folderType; 143 mAccount = account; 144 mResource = resource; 145 mIsSelected = isCurrentAccount; 146 mInflater = LayoutInflater.from(activity.getActivityContext()); 147 mType = type; 148 mIsEnabled = calculateEnabled(); 149 mImagesCache = cache; 150 mContactResolver = contactResolver; 151 } 152 153 /** 154 * Create a folder item with the given type. 155 * 156 * @param activity the underlying activity 157 * @param folder a folder that this item represents 158 * @param folderType one of {@link #FOLDER_INBOX}, {@link #FOLDER_RECENT} or 159 * {@link #FOLDER_OTHER} 160 * @return a drawer item for the folder. 161 */ 162 public static DrawerItem ofFolder(ControllableActivity activity, Folder folder, 163 int folderType) { 164 return new DrawerItem(VIEW_FOLDER, activity, folder, folderType, null, -1, false, 165 null, null); 166 } 167 168 private String folderToString() { 169 final StringBuilder sb = new StringBuilder("[DrawerItem "); 170 sb.append(" VIEW_FOLDER "); 171 sb.append(", mFolder="); 172 sb.append(mFolder); 173 sb.append(", mFolderType="); 174 sb.append(mFolderType); 175 sb.append("]"); 176 return sb.toString(); 177 } 178 179 /** 180 * Creates an item from an account. 181 * @param activity the underlying activity 182 * @param account the account to create a drawer item for 183 * @param unreadCount the unread count of the account, pass zero if 184 * @param isCurrentAccount true if the account is the current account, false otherwise 185 * @return a drawer item for the account. 186 */ 187 public static DrawerItem ofAccount(ControllableActivity activity, Account account, 188 int unreadCount, boolean isCurrentAccount, BitmapCache cache, 189 ContactResolver contactResolver) { 190 return new DrawerItem(VIEW_ACCOUNT, activity, null, ACCOUNT, account, unreadCount, 191 isCurrentAccount, cache, contactResolver); 192 } 193 194 private String accountToString() { 195 final StringBuilder sb = new StringBuilder("[DrawerItem "); 196 sb.append(" VIEW_ACCOUNT "); 197 sb.append(", mAccount="); 198 sb.append(mAccount); 199 sb.append("]"); 200 return sb.toString(); 201 } 202 203 /** 204 * Create a header item with a string resource. 205 * 206 * @param activity the underlying activity 207 * @param resource the string resource: R.string.all_folders_heading 208 * @return a drawer item for the header. 209 */ 210 public static DrawerItem ofHeader(ControllableActivity activity, int resource) { 211 return new DrawerItem(VIEW_HEADER, activity, null, INERT_HEADER, null, resource, false, 212 null, null); 213 } 214 215 private String headerToString() { 216 final StringBuilder sb = new StringBuilder("[DrawerItem "); 217 sb.append(" VIEW_HEADER "); 218 sb.append(", mResource="); 219 sb.append(mResource); 220 sb.append("]"); 221 return sb.toString(); 222 } 223 224 public static DrawerItem ofBlankHeader(ControllableActivity activity) { 225 return new DrawerItem(VIEW_BLANK_HEADER, activity, null, INERT_HEADER, null, 0, false, null, 226 null); 227 } 228 229 private String blankHeaderToString() { 230 return "[DrawerItem VIEW_BLANK_HEADER]"; 231 } 232 233 /** 234 * Create a "waiting for initialization" item. 235 * 236 * @param activity the underlying activity 237 * @return a drawer item with an indeterminate progress indicator. 238 */ 239 public static DrawerItem ofWaitView(ControllableActivity activity) { 240 return new DrawerItem(VIEW_WAITING_FOR_SYNC, activity, null, INERT_HEADER, null, -1, false, 241 null, null); 242 } 243 244 private static String waitToString() { 245 return "[DrawerItem VIEW_WAITING_FOR_SYNC ]"; 246 } 247 248 /** 249 * Returns a view for the given item. The method signature is identical to that required by a 250 * {@link android.widget.ListAdapter#getView(int, android.view.View, android.view.ViewGroup)}. 251 */ 252 public View getView(View convertView, ViewGroup parent) { 253 final View result; 254 switch (mType) { 255 case VIEW_FOLDER: 256 result = getFolderView(convertView, parent); 257 break; 258 case VIEW_HEADER: 259 result = getHeaderView(convertView, parent); 260 break; 261 case VIEW_BLANK_HEADER: 262 result = getBlankHeaderView(convertView, parent); 263 break; 264 case VIEW_ACCOUNT: 265 result = getAccountView(convertView, parent); 266 break; 267 case VIEW_WAITING_FOR_SYNC: 268 result = getEmptyView(convertView, parent); 269 break; 270 default: 271 LogUtils.wtf(LOG_TAG, "DrawerItem.getView(%d) for an invalid type!", mType); 272 result = null; 273 } 274 return result; 275 } 276 277 /** 278 * Book-keeping for how many different view types there are. Be sure to 279 * increment this appropriately once adding more types as drawer items 280 * @return number of different types of view items 281 */ 282 public static int getViewTypes() { 283 return LAST_FIELD; 284 } 285 286 /** 287 * Returns whether this view is enabled or not. An enabled view is one that accepts user taps 288 * and acts upon them. 289 * @return true if this view is enabled, false otherwise. 290 */ 291 public boolean isItemEnabled() { 292 return mIsEnabled; 293 } 294 295 /** Calculate whether the item is enabled */ 296 private boolean calculateEnabled() { 297 switch (mType) { 298 case VIEW_HEADER: 299 case VIEW_BLANK_HEADER: 300 // Headers are never enabled. 301 return false; 302 case VIEW_FOLDER: 303 // Folders are always enabled. 304 return true; 305 case VIEW_ACCOUNT: 306 // Accounts are always enabled. 307 return true; 308 case VIEW_WAITING_FOR_SYNC: 309 // Waiting for sync cannot be tapped, so never enabled. 310 return false; 311 default: 312 LogUtils.wtf(LOG_TAG, "DrawerItem.isItemEnabled() for invalid type %d", mType); 313 return false; 314 } 315 } 316 317 /** 318 * Returns whether this view is highlighted or not. 319 * 320 * 321 * @param currentFolder The current folder, according to the 322 * {@link com.android.mail.ui.FolderListFragment} 323 * @param currentType The type of the current folder. We want to only highlight a folder once. 324 * A folder might be in two places at once: in "All Folders", and in 325 * "Recent Folder". Valid types of selected folders are : 326 * {@link DrawerItem#FOLDER_INBOX}, {@link DrawerItem#FOLDER_RECENT} or 327 * {@link DrawerItem#FOLDER_OTHER}, or {@link DrawerItem#UNSET}. 328 329 * @return true if this DrawerItem results in a view that is highlighted (this DrawerItem is 330 * the current folder. 331 */ 332 public boolean isHighlighted(FolderUri currentFolder, int currentType) { 333 switch (mType) { 334 case VIEW_HEADER: 335 case VIEW_BLANK_HEADER: 336 // Headers are never highlighted 337 return false; 338 case VIEW_FOLDER: 339 // True if folder types and URIs are the same 340 if (currentFolder != null && mFolder != null && mFolder.folderUri != null) { 341 return (mFolderType == currentType) && mFolder.folderUri.equals(currentFolder); 342 } 343 return false; 344 case VIEW_ACCOUNT: 345 // Accounts are never highlighted 346 return false; 347 case VIEW_WAITING_FOR_SYNC: 348 // Waiting for sync cannot be tapped, so never highlighted. 349 return false; 350 default: 351 LogUtils.wtf(LOG_TAG, "DrawerItem.isHighlighted() for invalid type %d", mType); 352 return false; 353 } 354 } 355 356 /** 357 * Return a view for an account object. 358 * 359 * @param convertView a view, possibly null, to be recycled. 360 * @param parent the parent viewgroup to attach to. 361 * @return a view to display at this position. 362 */ 363 private View getAccountView(View convertView, ViewGroup parent) { 364 final AccountItemView accountItemView; 365 if (convertView != null) { 366 accountItemView = (AccountItemView) convertView; 367 } else { 368 accountItemView = 369 (AccountItemView) mInflater.inflate(R.layout.account_item, parent, false); 370 } 371 accountItemView.bind(mActivity.getActivityContext(), mAccount, mIsSelected, mImagesCache, 372 mContactResolver); 373 return accountItemView; 374 } 375 376 /** 377 * Returns a text divider between divisions. 378 * 379 * @param convertView a previous view, perhaps null 380 * @param parent the parent of this view 381 * @return a text header at the given position. 382 */ 383 private View getHeaderView(View convertView, ViewGroup parent) { 384 final View headerView; 385 if (convertView != null) { 386 headerView = convertView; 387 } else { 388 headerView = mInflater.inflate(R.layout.folder_list_header, parent, false); 389 } 390 final TextView textView = (TextView) headerView.findViewById(R.id.header_text); 391 textView.setText(mResource); 392 return headerView; 393 } 394 395 /** 396 * Returns a blank divider 397 * 398 * @param convertView A previous view, perhaps null 399 * @param parent the parent of this view 400 * @return a blank header 401 */ 402 private View getBlankHeaderView(View convertView, ViewGroup parent) { 403 final View blankHeaderView; 404 if (convertView != null) { 405 blankHeaderView = convertView; 406 } else { 407 blankHeaderView = mInflater.inflate(R.layout.folder_list_blank_header, parent, false); 408 } 409 return blankHeaderView; 410 } 411 412 /** 413 * Return a folder: either a parent folder or a normal (child or flat) 414 * folder. 415 * 416 * @param convertView a view, possibly null, to be recycled. 417 * @return a view showing a folder at the given position. 418 */ 419 private View getFolderView(View convertView, ViewGroup parent) { 420 final FolderItemView folderItemView; 421 if (convertView != null) { 422 folderItemView = (FolderItemView) convertView; 423 } else { 424 folderItemView = 425 (FolderItemView) mInflater.inflate(R.layout.folder_item, parent, false); 426 } 427 folderItemView.bind(mFolder, mActivity); 428 folderItemView.setIcon(mFolder); 429 return folderItemView; 430 } 431 432 /** 433 * Return a view for the 'Waiting for sync' item with the indeterminate progress indicator. 434 * 435 * @param convertView a view, possibly null, to be recycled. 436 * @param parent the parent hosting this view. 437 * @return a view for "Waiting for sync..." at given position. 438 */ 439 private View getEmptyView(View convertView, ViewGroup parent) { 440 final ViewGroup emptyView; 441 if (convertView != null) { 442 emptyView = (ViewGroup) convertView; 443 } else { 444 emptyView = (ViewGroup) mInflater.inflate(R.layout.drawer_empty_view, parent, false); 445 } 446 return emptyView; 447 } 448 449 } 450 451