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 toString()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 */ DrawerItem(int type, ControllableActivity activity, Folder folder, int folderType, Account account, int resource, boolean isCurrentAccount, BitmapCache cache, ContactResolver contactResolver)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 */ ofFolder(ControllableActivity activity, Folder folder, int folderType)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 folderToString()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 */ ofAccount(ControllableActivity activity, Account account, int unreadCount, boolean isCurrentAccount, BitmapCache cache, ContactResolver contactResolver)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 accountToString()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 */ ofHeader(ControllableActivity activity, int resource)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 headerToString()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 ofBlankHeader(ControllableActivity activity)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 blankHeaderToString()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 */ ofWaitView(ControllableActivity activity)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 waitToString()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 */ getView(View convertView, ViewGroup parent)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 */ getViewTypes()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 */ isItemEnabled()291 public boolean isItemEnabled() { 292 return mIsEnabled; 293 } 294 295 /** Calculate whether the item is enabled */ calculateEnabled()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 */ isHighlighted(FolderUri currentFolder, int currentType)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 */ getAccountView(View convertView, ViewGroup parent)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 */ getHeaderView(View convertView, ViewGroup parent)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 */ getBlankHeaderView(View convertView, ViewGroup parent)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 */ getFolderView(View convertView, ViewGroup parent)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 */ getEmptyView(View convertView, ViewGroup parent)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