Home | History | Annotate | Download | only in adapter
      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