Java tutorial
/* * Copyright (C) 2012 Google Inc. * Licensed to The Android Open Source Project. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* ========================================================================== *HISTORY * *Tag Date Author Description *============== ============ =============== ============================== *CONFLICT-50002 2014/10/24 zhaotianyong Modify the package conflict *CONFLICT-20001 2014/10/24 wenggangjin Modify the package conflict *BUGFIX-1052918 2015/07/31 zheng.zou [Email]The colour of the settings button display overlap when tap it. *BUGFIX-717280 2015/10/14 jian.xu [Android L][Email]The same folder in recent folder and all folder display different unread mail number *FEATURE-834751 2015/10/28 jian.xu Use different color to distinguish each account when in combined view mode ============================================================================ */ package com.tct.mail.ui; import android.app.Activity; import android.app.ListFragment; import android.app.LoaderManager; import android.content.Loader; import android.database.DataSetObserver; import android.net.Uri; import android.os.Bundle; import android.support.v4.widget.DrawerLayout; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.TextView; //TS: MOD by zhaotianyong for CONFLICT_50002 START //import com.android.bitmap.BitmapCache; //import com.android.bitmap.UnrefedBitmapCache; import com.tct.fw.bitmap.BitmapCache; import com.tct.fw.bitmap.UnrefedBitmapCache; //TS: MOD by zhaotianyong for CONFLICT_50002 END import com.tct.email.R; import com.tct.mail.utils.LogTag; import com.tct.mail.utils.LogUtils; //TS: MOD by wenggangjin for CONFLICT_20001 START //import com.google.common.collect.Lists; import com.tct.fw.google.common.collect.Lists; import com.tct.mail.adapter.DrawerItem; import com.tct.mail.analytics.Analytics; import com.tct.mail.bitmap.ContactResolver; import com.tct.mail.browse.MergedAdapter; import com.tct.mail.content.ObjectCursor; import com.tct.mail.content.ObjectCursorLoader; import com.tct.mail.providers.Account; import com.tct.mail.providers.AccountObserver; import com.tct.mail.providers.AllAccountObserver; import com.tct.mail.providers.Folder; import com.tct.mail.providers.FolderObserver; import com.tct.mail.providers.FolderWatcher; import com.tct.mail.providers.RecentFolderObserver; import com.tct.mail.providers.UIProvider; import com.tct.mail.providers.UIProvider.FolderType; import com.tct.mail.utils.FolderUri; import com.tct.mail.utils.Utils; //TS: MOD by wenggangjin for CONFLICT_20001 END import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * This fragment shows the list of folders and the list of accounts. Prior to June 2013, * the mail application had a spinner in the top action bar. Now, the list of accounts is displayed * in a drawer along with the list of folders. * * This class has the following use-cases: * <ul> * <li> * Show a list of accounts and a divided list of folders. In this case, the list shows * Accounts, Inboxes, Recent Folders, All folders, Help, and Feedback. * Tapping on Accounts takes the user to the default Inbox for that account. Tapping on * folders switches folders. Tapping on Help takes the user to HTML help pages. Tapping on * Feedback takes the user to a screen for submitting text and a screenshot of the * application to a feedback system. * This is created through XML resources as a {@link DrawerFragment}. Since it is created * through resources, it receives all arguments through callbacks. * </li> * <li> * Show a list of folders for a specific level. At the top-level, this shows Inbox, Sent, * Drafts, Starred, and any user-created folders. For providers that allow nested folders, * this will only show the folders at the top-level. * <br /> Tapping on a parent folder creates a new fragment with the child folders at * that level. * </li> * <li> * Shows a list of folders that can be turned into widgets/shortcuts. This is used by the * {@link FolderSelectionActivity} to allow the user to create a shortcut or widget for * any folder for a given account. * </li> * </ul> */ public class FolderListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<ObjectCursor<Folder>>, FolderWatcher.UnreadCountChangedListener { private static final String LOG_TAG = LogTag.getLogTag(); /** The parent activity */ protected ControllableActivity mActivity; /** The underlying list view */ private ListView mListView; /** URI that points to the list of folders for the current account. */ private Uri mFolderListUri; /** * True if you want a divided FolderList. A divided folder list shows the following groups: * Inboxes, Recent Folders, All folders. * * An undivided FolderList shows all folders without any divisions and without recent folders. * This is true only for the drawer: for all others it is false. */ protected boolean mIsDivided = false; /** * True if the folder list belongs to a folder selection activity (one account only) * and the footer should not show. */ protected boolean mIsFolderSelectionActivity = true; /** An {@link ArrayList} of {@link FolderType}s to exclude from displaying. */ private ArrayList<Integer> mExcludedFolderTypes; /** Object that changes folders on our behalf. */ private FolderSelector mFolderChanger; /** Object that changes accounts on our behalf */ private AccountController mAccountController; private DrawerController mDrawerController; /** The currently selected folder (the folder being viewed). This is never null. */ private FolderUri mSelectedFolderUri = FolderUri.EMPTY; /** * The current folder from the controller. This is meant only to check when the unread count * goes out of sync and fixing it. */ private Folder mCurrentFolderForUnreadCheck; /** Parent of the current folder, or null if the current folder is not a child. */ private Folder mParentFolder; private static final int FOLDER_LIST_LOADER_ID = 0; /** Loader id for the list of all folders in the account */ private static final int ALL_FOLDER_LIST_LOADER_ID = 1; /** Key to store {@link #mParentFolder}. */ private static final String ARG_PARENT_FOLDER = "arg-parent-folder"; /** Key to store {@link #mFolderListUri}. */ private static final String ARG_FOLDER_LIST_URI = "arg-folder-list-uri"; /** Key to store {@link #mExcludedFolderTypes} */ private static final String ARG_EXCLUDED_FOLDER_TYPES = "arg-excluded-folder-types"; private static final String BUNDLE_LIST_STATE = "flf-list-state"; private static final String BUNDLE_SELECTED_FOLDER = "flf-selected-folder"; private static final String BUNDLE_SELECTED_ITEM_TYPE = "flf-selected-item-type"; private static final String BUNDLE_SELECTED_TYPE = "flf-selected-type"; private static final String BUNDLE_INBOX_PRESENT = "flf-inbox-present"; /** Number of avatars to we whould like to fit in the avatar cache */ private static final int IMAGE_CACHE_COUNT = 10; /** * This is the fractional portion of the total cache size above that's dedicated to non-pooled * bitmaps. (This is basically the portion of cache dedicated to GIFs.) */ private static final float AVATAR_IMAGES_PREVIEWS_CACHE_NON_POOLED_FRACTION = 0f; /** Each string has upper estimate of 50 bytes, so this cache would be 5KB. */ private static final int AVATAR_IMAGES_PREVIEWS_CACHE_NULL_CAPACITY = 100; /** Adapter used by the list that wraps both the folder adapter and the accounts adapter. */ private MergedAdapter<ListAdapter> mMergedAdapter; /** Adapter containing the list of accounts. */ private AccountsAdapter mAccountsAdapter; /** Adapter containing the list of folders and, optionally, headers and the wait view. */ private FolderListFragmentCursorAdapter mFolderAdapter; /** Adapter containing the Help and Feedback views */ private FooterAdapter mFooterAdapter; /** Observer to wait for changes to the current folder so we can change the selected folder */ private FolderObserver mFolderObserver = null; /** Listen for account changes. */ private AccountObserver mAccountObserver = null; /** Listen to changes to selected folder or account */ private FolderOrAccountListener mFolderOrAccountListener = null; /** Listen to changes to list of all accounts */ private AllAccountObserver mAllAccountsObserver = null; /** * Type of currently selected folder: {@link DrawerItem#FOLDER_INBOX}, * {@link DrawerItem#FOLDER_RECENT} or {@link DrawerItem#FOLDER_OTHER}. * Set as {@link DrawerItem#UNSET} to begin with, as there is nothing selected yet. */ private int mSelectedDrawerItemType = DrawerItem.UNSET; /** The FolderType of the selected folder {@link FolderType} */ private int mSelectedFolderType = FolderType.INBOX; /** The current account according to the controller */ protected Account mCurrentAccount; /** The account we will change to once the drawer (if any) is closed */ private Account mNextAccount = null; /** The folder we will change to once the drawer (if any) is closed */ private Folder mNextFolder = null; /** Watcher for tracking and receiving unread counts for mail */ private FolderWatcher mFolderWatcher = null; private boolean mRegistered = false; private final DrawerStateListener mDrawerListener = new DrawerStateListener(); private BitmapCache mImagesCache; private ContactResolver mContactResolver; private boolean mInboxPresent; private boolean mMiniDrawerEnabled; private boolean mIsMinimized; private MiniDrawerView mMiniDrawerView; /** * Constructor needs to be public to handle orientation changes and activity lifecycle events. */ public FolderListFragment() { super(); } @Override public String toString() { final StringBuilder sb = new StringBuilder(super.toString()); sb.setLength(sb.length() - 1); sb.append(" folder="); sb.append(mFolderListUri); sb.append(" parent="); sb.append(mParentFolder); sb.append(" adapterCount="); sb.append(mMergedAdapter != null ? mMergedAdapter.getCount() : -1); sb.append("}"); return sb.toString(); } /** * Creates a new instance of {@link FolderListFragment}, initialized * to display the folder and its immediate children. * @param folder parent folder whose children are shown * */ public static FolderListFragment ofTree(Folder folder) { final FolderListFragment fragment = new FolderListFragment(); fragment.setArguments(getBundleFromArgs(folder, folder.childFoldersListUri, null)); return fragment; } /** * Creates a new instance of {@link FolderListFragment}, initialized * to display the top level: where we have no parent folder, but we have a list of folders * from the account. * @param folderListUri the URI which contains all the list of folders * @param excludedFolderTypes A list of {@link FolderType}s to exclude from displaying */ public static FolderListFragment ofTopLevelTree(Uri folderListUri, final ArrayList<Integer> excludedFolderTypes) { final FolderListFragment fragment = new FolderListFragment(); fragment.setArguments(getBundleFromArgs(null, folderListUri, excludedFolderTypes)); return fragment; } /** * Construct a bundle that represents the state of this fragment. * * @param parentFolder non-null for trees, the parent of this list * @param folderListUri the URI which contains all the list of folders * @param excludedFolderTypes if non-null, this indicates folders to exclude in lists. * @return Bundle containing parentFolder, divided list boolean and * excluded folder types */ private static Bundle getBundleFromArgs(Folder parentFolder, Uri folderListUri, final ArrayList<Integer> excludedFolderTypes) { final Bundle args = new Bundle(3); if (parentFolder != null) { args.putParcelable(ARG_PARENT_FOLDER, parentFolder); } if (folderListUri != null) { args.putString(ARG_FOLDER_LIST_URI, folderListUri.toString()); } if (excludedFolderTypes != null) { args.putIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES, excludedFolderTypes); } return args; } @Override public void onActivityCreated(Bundle savedState) { super.onActivityCreated(savedState); // Strictly speaking, we get back an android.app.Activity from getActivity. However, the // only activity creating a ConversationListContext is a MailActivity which is of type // ControllableActivity, so this cast should be safe. If this cast fails, some other // activity is creating ConversationListFragments. This activity must be of type // ControllableActivity. final Activity activity = getActivity(); if (!(activity instanceof ControllableActivity)) { LogUtils.wtf(LOG_TAG, "FolderListFragment expects only a ControllableActivity to" + "create it. Cannot proceed."); return; } mActivity = (ControllableActivity) activity; final int avatarSize = getActivity().getResources().getDimensionPixelSize(R.dimen.account_avatar_dimension); mImagesCache = new UnrefedBitmapCache( Utils.isLowRamDevice(getActivity()) ? 0 : avatarSize * avatarSize * IMAGE_CACHE_COUNT, AVATAR_IMAGES_PREVIEWS_CACHE_NON_POOLED_FRACTION, AVATAR_IMAGES_PREVIEWS_CACHE_NULL_CAPACITY); mContactResolver = new ContactResolver(getActivity().getContentResolver(), mImagesCache); mMiniDrawerView.setController(this); if (!mMiniDrawerEnabled) { mMiniDrawerView.setVisibility(View.GONE); } else { // set up initial state setMinimized(isMinimized()); } final FolderController controller = mActivity.getFolderController(); // Listen to folder changes in the future mFolderObserver = new FolderObserver() { @Override public void onChanged(Folder newFolder) { setSelectedFolder(newFolder); } }; final Folder currentFolder; if (controller != null) { // Only register for selected folder updates if we have a controller. currentFolder = mFolderObserver.initialize(controller); mCurrentFolderForUnreadCheck = currentFolder; } else { currentFolder = null; } // Initialize adapter for folder/hierarchical list. Note this relies on // mActivity being initialized. final Folder selectedFolder; if (mParentFolder != null) { mFolderAdapter = new HierarchicalFolderListAdapter(null, mParentFolder); selectedFolder = mActivity.getHierarchyFolder(); } else { mFolderAdapter = new FolderAdapter(mIsDivided); selectedFolder = currentFolder; } mAccountsAdapter = newAccountsAdapter(); mFooterAdapter = new FooterAdapter(); // Is the selected folder fresher than the one we have restored from a bundle? if (selectedFolder != null && !selectedFolder.folderUri.equals(mSelectedFolderUri)) { setSelectedFolder(selectedFolder); } // Assign observers for current account & all accounts final AccountController accountController = mActivity.getAccountController(); mAccountObserver = new AccountObserver() { @Override public void onChanged(Account newAccount) { setSelectedAccount(newAccount); } }; mFolderChanger = mActivity.getFolderSelector(); if (accountController != null) { mAccountController = accountController; // Current account and its observer. setSelectedAccount(mAccountObserver.initialize(accountController)); // List of all accounts and its observer. mAllAccountsObserver = new AllAccountObserver() { @Override public void onChanged(Account[] allAccounts) { if (!mRegistered && mAccountController != null) { // TODO(viki): Round-about way of setting the watcher. http://b/8750610 mAccountController.setFolderWatcher(mFolderWatcher); mRegistered = true; } mFolderWatcher.updateAccountList(getAllAccounts()); rebuildAccountList(); if (mMiniDrawerEnabled) { mMiniDrawerView.refresh(); } } }; mAllAccountsObserver.initialize(accountController); mFolderOrAccountListener = new FolderOrAccountListener(); mAccountController.registerFolderOrAccountChangedObserver(mFolderOrAccountListener); final DrawerController dc = mActivity.getDrawerController(); if (dc != null) { dc.registerDrawerListener(mDrawerListener); } } mDrawerController = mActivity.getDrawerController(); if (mActivity.isFinishing()) { // Activity is finishing, just bail. return; } mListView.setChoiceMode(getListViewChoiceMode()); mMergedAdapter = new MergedAdapter<ListAdapter>(); if (mAccountsAdapter != null) { mMergedAdapter.setAdapters(mAccountsAdapter, mFolderAdapter, mFooterAdapter); } else { mMergedAdapter.setAdapters(mFolderAdapter, mFooterAdapter); } mFolderWatcher = new FolderWatcher(mActivity, this); mFolderWatcher.updateAccountList(getAllAccounts()); setListAdapter(mMergedAdapter); } public BitmapCache getBitmapCache() { return mImagesCache; } public ContactResolver getContactResolver() { return mContactResolver; } public void toggleDrawerState() { if (mDrawerController != null) { mDrawerController.toggleDrawerState(); } } /** * Set the instance variables from the arguments provided here. * @param args bundle of arguments with keys named ARG_* */ private void setInstanceFromBundle(Bundle args) { if (args == null) { return; } mParentFolder = args.getParcelable(ARG_PARENT_FOLDER); final String folderUri = args.getString(ARG_FOLDER_LIST_URI); if (folderUri != null) { mFolderListUri = Uri.parse(folderUri); } mExcludedFolderTypes = args.getIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { setInstanceFromBundle(getArguments()); final View rootView = inflater.inflate(R.layout.folder_list, container, false); mListView = (ListView) rootView.findViewById(android.R.id.list); mListView.setEmptyView(null); mListView.setDivider(null); addListHeader(inflater, mListView); if (savedState != null && savedState.containsKey(BUNDLE_LIST_STATE)) { mListView.onRestoreInstanceState(savedState.getParcelable(BUNDLE_LIST_STATE)); } if (savedState != null && savedState.containsKey(BUNDLE_SELECTED_FOLDER)) { mSelectedFolderUri = new FolderUri(Uri.parse(savedState.getString(BUNDLE_SELECTED_FOLDER))); mSelectedDrawerItemType = savedState.getInt(BUNDLE_SELECTED_ITEM_TYPE); mSelectedFolderType = savedState.getInt(BUNDLE_SELECTED_TYPE); } else if (mParentFolder != null) { mSelectedFolderUri = mParentFolder.folderUri; // No selected folder type required for hierarchical lists. } if (savedState != null) { mInboxPresent = savedState.getBoolean(BUNDLE_INBOX_PRESENT, true); } else { mInboxPresent = true; } mMiniDrawerView = (MiniDrawerView) rootView.findViewById(R.id.mini_drawer); return rootView; } protected void addListHeader(LayoutInflater inflater, ListView list) { // Default impl does nothing } @Override public void onStart() { super.onStart(); } @Override public void onStop() { super.onStop(); } @Override public void onPause() { super.onPause(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mListView != null) { outState.putParcelable(BUNDLE_LIST_STATE, mListView.onSaveInstanceState()); } if (mSelectedFolderUri != null) { outState.putString(BUNDLE_SELECTED_FOLDER, mSelectedFolderUri.toString()); } outState.putInt(BUNDLE_SELECTED_ITEM_TYPE, mSelectedDrawerItemType); outState.putInt(BUNDLE_SELECTED_TYPE, mSelectedFolderType); outState.putBoolean(BUNDLE_INBOX_PRESENT, mInboxPresent); } @Override public void onDestroyView() { if (mFolderAdapter != null) { mFolderAdapter.destroy(); } // Clear the adapter. setListAdapter(null); if (mFolderObserver != null) { mFolderObserver.unregisterAndDestroy(); mFolderObserver = null; } if (mAccountObserver != null) { mAccountObserver.unregisterAndDestroy(); mAccountObserver = null; } if (mAllAccountsObserver != null) { mAllAccountsObserver.unregisterAndDestroy(); mAllAccountsObserver = null; } if (mFolderOrAccountListener != null && mAccountController != null) { mAccountController.unregisterFolderOrAccountChangedObserver(mFolderOrAccountListener); mFolderOrAccountListener = null; } super.onDestroyView(); if (mActivity != null) { final DrawerController dc = mActivity.getDrawerController(); if (dc != null) { dc.unregisterDrawerListener(mDrawerListener); } } } @Override public void onListItemClick(ListView l, View v, int position, long id) { viewFolderOrChangeAccount(position); } private Folder getDefaultInbox(Account account) { if (account == null || mFolderWatcher == null) { return null; } return mFolderWatcher.getDefaultInbox(account); } protected int getUnreadCount(Account account) { if (account == null || mFolderWatcher == null) { return 0; } return mFolderWatcher.getUnreadCount(account); } protected void changeAccount(final Account account) { // Switching accounts takes you to the default inbox for that account. mSelectedDrawerItemType = DrawerItem.FOLDER_INBOX; mSelectedFolderType = FolderType.INBOX; mNextAccount = account; mAccountController.closeDrawer(true, mNextAccount, getDefaultInbox(mNextAccount)); Analytics.getInstance().sendEvent("switch_account", "drawer_account_switch", null, 0); } /** * Display the conversation list from the folder at the position given. * @param position a zero indexed position into the list. */ protected void viewFolderOrChangeAccount(int position) { // Get the ListView's adapter final Object item = getListView().getAdapter().getItem(position); LogUtils.d(LOG_TAG, "viewFolderOrChangeAccount(%d): %s", position, item); final Folder folder; int folderType = DrawerItem.UNSET; if (item instanceof DrawerItem) { final DrawerItem drawerItem = (DrawerItem) item; // Could be a folder or account. final int itemType = drawerItem.mType; if (itemType == DrawerItem.VIEW_ACCOUNT) { // Account, so switch. folder = null; onAccountSelected(drawerItem.mAccount); } else if (itemType == DrawerItem.VIEW_FOLDER) { // Folder type, so change folders only. folder = drawerItem.mFolder; mSelectedDrawerItemType = folderType = drawerItem.mFolderType; mSelectedFolderType = folder.type; LogUtils.d(LOG_TAG, "FLF.viewFolderOrChangeAccount folder=%s, type=%d", folder, mSelectedDrawerItemType); } else { // Do nothing. LogUtils.d(LOG_TAG, "FolderListFragment: viewFolderOrChangeAccount():" + " Clicked on unset item in drawer. Offending item is " + item); return; } } else if (item instanceof Folder) { folder = (Folder) item; } else if (item instanceof FooterItem) { folder = null; ((FooterItem) item).onClick(null /* unused */); } else { // Don't know how we got here. LogUtils.wtf(LOG_TAG, "viewFolderOrChangeAccount(): invalid item"); folder = null; } if (folder != null) { final String label = (folderType == DrawerItem.FOLDER_RECENT) ? "recent" : "normal"; onFolderSelected(folder, label); } } public void onFolderSelected(Folder folder, String analyticsLabel) { // Go to the conversation list for this folder. if (!folder.folderUri.equals(mSelectedFolderUri)) { mNextFolder = folder; mAccountController.closeDrawer(true /** hasNewFolderOrAccount */ , null /** nextAccount */ , folder /** nextFolder */ ); Analytics.getInstance().sendEvent("switch_folder", folder.getTypeDescription(), analyticsLabel, 0); } else { // Clicked on same folder, just close drawer mAccountController.closeDrawer(false /** hasNewFolderOrAccount */ , null /** nextAccount */ , folder /** nextFolder */ ); } } public void onAccountSelected(Account account) { // Only reset the cache if the account has changed. if (mCurrentAccount == null || account == null || !mCurrentAccount.getEmailAddress().equals(account.getEmailAddress())) { mActivity.resetSenderImageCache(); } if (account != null && mSelectedFolderUri.equals(account.settings.defaultInbox)) { // We're already in the default inbox for account, // just close the drawer (no new target folders/accounts) mAccountController.closeDrawer(false, mNextAccount, getDefaultInbox(mNextAccount)); } else { changeAccount(account); } } @Override public Loader<ObjectCursor<Folder>> onCreateLoader(int id, Bundle args) { final Uri folderListUri; if (id == FOLDER_LIST_LOADER_ID) { if (mFolderListUri != null) { // Folder trees, they specify a URI at construction time. folderListUri = mFolderListUri; } else { // Drawers get the folder list from the current account. folderListUri = mCurrentAccount.folderListUri; } } else if (id == ALL_FOLDER_LIST_LOADER_ID) { folderListUri = mCurrentAccount.allFolderListUri; } else { LogUtils.wtf(LOG_TAG, "FLF.onCreateLoader() with weird type"); return null; } return new ObjectCursorLoader<Folder>(mActivity.getActivityContext(), folderListUri, UIProvider.FOLDERS_PROJECTION, Folder.FACTORY); } @Override public void onLoadFinished(Loader<ObjectCursor<Folder>> loader, ObjectCursor<Folder> data) { if (mFolderAdapter != null) { if (loader.getId() == FOLDER_LIST_LOADER_ID) { mFolderAdapter.setCursor(data); if (mMiniDrawerEnabled) { mMiniDrawerView.refresh(); } } else if (loader.getId() == ALL_FOLDER_LIST_LOADER_ID) { mFolderAdapter.setAllFolderListCursor(data); } } } @Override public void onLoaderReset(Loader<ObjectCursor<Folder>> loader) { if (mFolderAdapter != null) { if (loader.getId() == FOLDER_LIST_LOADER_ID) { mFolderAdapter.setCursor(null); } else if (loader.getId() == ALL_FOLDER_LIST_LOADER_ID) { mFolderAdapter.setAllFolderListCursor(null); } } } /** * Returns the sorted list of accounts. The AAC always has the current list, sorted by * frequency of use. * @return a list of accounts, sorted by frequency of use */ public Account[] getAllAccounts() { if (mAllAccountsObserver != null) { return mAllAccountsObserver.getAllAccounts(); } return new Account[0]; } protected AccountsAdapter newAccountsAdapter() { return new AccountsAdapter(); } @Override public void onUnreadCountChange() { if (mAccountsAdapter != null) { mAccountsAdapter.notifyDataSetChanged(); } } public boolean isMiniDrawerEnabled() { return mMiniDrawerEnabled; } public void setMiniDrawerEnabled(boolean enabled) { mMiniDrawerEnabled = enabled; setMinimized(isMinimized()); // init visual state } public boolean isMinimized() { return mMiniDrawerEnabled && mIsMinimized; } public void setMinimized(boolean minimized) { if (!mMiniDrawerEnabled) { return; } mIsMinimized = minimized; if (isMinimized()) { mMiniDrawerView.setVisibility(View.VISIBLE); mListView.setVisibility(View.INVISIBLE); } else { mMiniDrawerView.setVisibility(View.INVISIBLE); mListView.setVisibility(View.VISIBLE); mListView.requestFocus(); } } /** * Interface for all cursor adapters that allow setting a cursor and being destroyed. */ private interface FolderListFragmentCursorAdapter extends ListAdapter { /** Update the folder list cursor with the cursor given here. */ void setCursor(ObjectCursor<Folder> cursor); ObjectCursor<Folder> getCursor(); /** Update the all folder list cursor with the cursor given here. */ void setAllFolderListCursor(ObjectCursor<Folder> cursor); /** Remove all observers and destroy the object. */ void destroy(); /** Notifies the adapter that the data has changed. */ void notifyDataSetChanged(); } /** * An adapter for flat folder lists. */ private class FolderAdapter extends BaseAdapter implements FolderListFragmentCursorAdapter { private final RecentFolderObserver mRecentFolderObserver = new RecentFolderObserver() { @Override public void onChanged() { if (!isCursorInvalid()) { rebuildFolderList(); } } }; /** No resource used for string header in folder list */ private static final int BLANK_HEADER_RESOURCE = -1; /** Cache of most recently used folders */ private final RecentFolderList mRecentFolders; /** True if the list is divided, false otherwise. See the comment on * {@link FolderListFragment#mIsDivided} for more information */ private final boolean mIsDivided; /** All the items */ private List<DrawerItem> mItemList = new ArrayList<DrawerItem>(); /** Cursor into the folder list. This might be null. */ private ObjectCursor<Folder> mCursor = null; /** Cursor into the all folder list. This might be null. */ private ObjectCursor<Folder> mAllFolderListCursor = null; /** * Creates a {@link FolderAdapter}. This is a list of all the accounts and folders. * * @param isDivided true if folder list is flat, false if divided by label group. See * the comments on {@link #mIsDivided} for more information */ public FolderAdapter(boolean isDivided) { super(); mIsDivided = isDivided; final RecentFolderController controller = mActivity.getRecentFolderController(); if (controller != null && mIsDivided) { mRecentFolders = mRecentFolderObserver.initialize(controller); } else { mRecentFolders = null; } } @Override public View getView(int position, View convertView, ViewGroup parent) { final DrawerItem item = (DrawerItem) getItem(position); final View view = item.getView(convertView, parent); final int type = item.mType; final boolean isSelected = item.isHighlighted(mSelectedFolderUri, mSelectedDrawerItemType); if (type == DrawerItem.VIEW_FOLDER) { mListView.setItemChecked((mAccountsAdapter != null ? mAccountsAdapter.getCount() : 0) + position + mListView.getHeaderViewsCount(), isSelected); } // If this is the current folder, also check to verify that the unread count // matches what the action bar shows. if (type == DrawerItem.VIEW_FOLDER && isSelected && (mCurrentFolderForUnreadCheck != null) && item.mFolder.unreadCount != mCurrentFolderForUnreadCheck.unreadCount) { ((FolderItemView) view).overrideUnreadCount(mCurrentFolderForUnreadCheck.unreadCount); } return view; } @Override public int getViewTypeCount() { // Accounts, headers, folders (all parts of drawer view types) return DrawerItem.getViewTypes(); } @Override public int getItemViewType(int position) { return ((DrawerItem) getItem(position)).mType; } @Override public int getCount() { return mItemList.size(); } @Override public boolean isEnabled(int position) { final DrawerItem drawerItem = ((DrawerItem) getItem(position)); return drawerItem != null && drawerItem.isItemEnabled(); } @Override public boolean areAllItemsEnabled() { // We have headers and thus some items are not enabled. return false; } /** * Returns all the recent folders from the list given here. Safe to call with a null list. * @param recentList a list of all recently accessed folders. * @return a valid list of folders, which are all recent folders. */ private List<Folder> getRecentFolders(RecentFolderList recentList) { final List<Folder> folderList = new ArrayList<Folder>(); if (recentList == null) { return folderList; } // Get all recent folders, after removing system folders. for (final Folder f : recentList.getRecentFolderList(null)) { if (!f.isProviderFolder()) { folderList.add(f); } } return folderList; } /** * Responsible for verifying mCursor, and ensuring any recalculate * conditions are met. Also calls notifyDataSetChanged once it's finished * populating {@link com.tct.mail.ui.FolderListFragment.FolderAdapter#mItemList} */ private void rebuildFolderList() { final boolean oldInboxPresent = mInboxPresent; mItemList = recalculateListFolders(); if (mAccountController != null && mInboxPresent && !oldInboxPresent) { // We didn't have an inbox folder before, but now we do. This can occur when // setting up a new account. We automatically create the "starred" virtual // virtual folder, but we won't create the inbox until it gets synced. // This means that we'll start out looking at the "starred" folder, and the // user will need to manually switch to the inbox. See b/13793316 mAccountController.switchToDefaultInboxOrChangeAccount(mCurrentAccount); } // Ask the list to invalidate its views. notifyDataSetChanged(); } /** * Recalculates the system, recent and user label lists. * This method modifies all the three lists on every single invocation. */ private List<DrawerItem> recalculateListFolders() { final List<DrawerItem> itemList = new ArrayList<DrawerItem>(); // If we are waiting for folder initialization, we don't have any kinds of folders, // just the "Waiting for initialization" item. Note, this should only be done // when we're waiting for account initialization or initial sync. if (isCursorInvalid()) { if (!mCurrentAccount.isAccountReady()) { itemList.add(DrawerItem.ofWaitView(mActivity)); } return itemList; } if (!mIsDivided) { // Adapter for a flat list. Everything is a FOLDER_OTHER, and there are no headers. do { final Folder f = mCursor.getModel(); if (!isFolderTypeExcluded(f)) { itemList.add(DrawerItem.ofFolder(mActivity, f, DrawerItem.FOLDER_OTHER)); } } while (mCursor.moveToNext()); return itemList; } // Otherwise, this is an adapter for a divided list. final List<DrawerItem> allFoldersList = new ArrayList<DrawerItem>(); final List<DrawerItem> inboxFolders = new ArrayList<DrawerItem>(); do { final Folder f = mCursor.getModel(); if (!isFolderTypeExcluded(f)) { if (f.isInbox()) { inboxFolders.add(DrawerItem.ofFolder(mActivity, f, DrawerItem.FOLDER_INBOX)); } else { allFoldersList.add(DrawerItem.ofFolder(mActivity, f, DrawerItem.FOLDER_OTHER)); } } } while (mCursor.moveToNext()); //TS: jian.xu 2015-10-14 EMAIL BUGFIX-717280 ADD_S //Note: Update recent folder cache if (mRecentFolders != null) { mRecentFolders.updateFolderCache(mCursor); } //TS: jian.xu 2015-10-14 EMAIL BUGFIX-717280 ADD_E // If we have the all folder list, verify that the current folder exists boolean currentFolderFound = false; if (mAllFolderListCursor != null) { final String folderName = mSelectedFolderUri.toString(); LogUtils.d(LOG_TAG, "Checking if all folder list contains %s", folderName); if (mAllFolderListCursor.moveToFirst()) { LogUtils.d(LOG_TAG, "Cursor for %s seems reasonably valid", folderName); do { final Folder f = mAllFolderListCursor.getModel(); if (!isFolderTypeExcluded(f)) { if (f.folderUri.equals(mSelectedFolderUri)) { LogUtils.d(LOG_TAG, "Found %s !", folderName); currentFolderFound = true; } } } while (!currentFolderFound && mAllFolderListCursor.moveToNext()); } // The search folder will not be found here because it is excluded from the drawer. // Don't switch off from the current folder if it's search. if (!currentFolderFound && !Folder.isType(FolderType.SEARCH, mSelectedFolderType) && mSelectedFolderUri != FolderUri.EMPTY && mCurrentAccount != null && mAccountController != null && mAccountController.isDrawerPullEnabled()) { LogUtils.d(LOG_TAG, "Current folder (%1$s) has disappeared for %2$s", folderName, mCurrentAccount.getEmailAddress()); changeAccount(mCurrentAccount); } } mInboxPresent = (inboxFolders.size() > 0); // Add all inboxes (sectioned Inboxes included) before recent folders. addFolderDivision(itemList, inboxFolders, BLANK_HEADER_RESOURCE); // Add recent folders next. addRecentsToList(itemList); // Add the remaining folders. addFolderDivision(itemList, allFoldersList, R.string.all_folders_heading); return itemList; } /** * Given a list of folders as {@link DrawerItem}s, add them as a group. * Passing in a non-0 integer for the resource will enable a header. * * @param destination List of drawer items to populate * @param source List of drawer items representing folders to add to the drawer * @param headerStringResource * {@link FolderAdapter#BLANK_HEADER_RESOURCE} if no header text * is required, or res-id otherwise. The integer is interpreted as the string * for the header's title. */ private void addFolderDivision(List<DrawerItem> destination, List<DrawerItem> source, int headerStringResource) { if (source.size() > 0) { if (headerStringResource != BLANK_HEADER_RESOURCE) { destination.add(DrawerItem.ofHeader(mActivity, headerStringResource)); } else { destination.add(DrawerItem.ofBlankHeader(mActivity)); } destination.addAll(source); } } /** * Add recent folders to the list in order as acquired by the {@link RecentFolderList}. * * @param destination List of drawer items to populate */ private void addRecentsToList(List<DrawerItem> destination) { // If there are recent folders, add them. final List<Folder> recentFolderList = getRecentFolders(mRecentFolders); // Remove any excluded folder types if (mExcludedFolderTypes != null) { final Iterator<Folder> iterator = recentFolderList.iterator(); while (iterator.hasNext()) { if (isFolderTypeExcluded(iterator.next())) { iterator.remove(); } } } if (recentFolderList.size() > 0) { destination.add(DrawerItem.ofHeader(mActivity, R.string.recent_folders_heading)); // Recent folders are not queried for position. for (Folder f : recentFolderList) { destination.add(DrawerItem.ofFolder(mActivity, f, DrawerItem.FOLDER_RECENT)); } } } /** * Check if the cursor provided is valid. * @return True if cursor is invalid, false otherwise */ private boolean isCursorInvalid() { return mCursor == null || mCursor.isClosed() || mCursor.getCount() <= 0 || !mCursor.moveToFirst(); } @Override public void setCursor(ObjectCursor<Folder> cursor) { mCursor = cursor; rebuildAccountList(); rebuildFolderList(); } @Override public ObjectCursor<Folder> getCursor() { return mCursor; } @Override public void setAllFolderListCursor(final ObjectCursor<Folder> cursor) { mAllFolderListCursor = cursor; rebuildAccountList(); rebuildFolderList(); } @Override public Object getItem(int position) { // Is there an attempt made to access outside of the drawer item list? if (position >= mItemList.size()) { return null; } else { return mItemList.get(position); } } @Override public long getItemId(int position) { return getItem(position).hashCode(); } @Override public final void destroy() { mRecentFolderObserver.unregisterAndDestroy(); } } private class HierarchicalFolderListAdapter extends ArrayAdapter<Folder> implements FolderListFragmentCursorAdapter { private static final int PARENT = 0; private static final int CHILD = 1; private final FolderUri mParentUri; private final Folder mParent; private final FolderItemView.DropHandler mDropHandler; public HierarchicalFolderListAdapter(ObjectCursor<Folder> c, Folder parentFolder) { super(mActivity.getActivityContext(), R.layout.folder_item); mDropHandler = mActivity; mParent = parentFolder; mParentUri = parentFolder.folderUri; setCursor(c); } @Override public int getViewTypeCount() { // Child and Parent return 2; } @Override public int getItemViewType(int position) { final Folder f = getItem(position); return f.folderUri.equals(mParentUri) ? PARENT : CHILD; } @Override public View getView(int position, View convertView, ViewGroup parent) { final FolderItemView folderItemView; final Folder folder = getItem(position); boolean isParent = folder.folderUri.equals(mParentUri); if (convertView != null) { folderItemView = (FolderItemView) convertView; } else { int resId = isParent ? R.layout.folder_item : R.layout.child_folder_item; folderItemView = (FolderItemView) LayoutInflater.from(mActivity.getActivityContext()).inflate(resId, null); } folderItemView.bind(folder, mDropHandler); if (folder.folderUri.equals(mSelectedFolderUri)) { final ListView listView = getListView(); listView.setItemChecked((mAccountsAdapter != null ? mAccountsAdapter.getCount() : 0) + position + listView.getHeaderViewsCount(), true); // If this is the current folder, also check to verify that the unread count // matches what the action bar shows. final boolean unreadCountDiffers = (mCurrentFolderForUnreadCheck != null) && folder.unreadCount != mCurrentFolderForUnreadCheck.unreadCount; if (unreadCountDiffers) { folderItemView.overrideUnreadCount(mCurrentFolderForUnreadCheck.unreadCount); } } Folder.setFolderBlockColor(folder, folderItemView.findViewById(R.id.color_block)); Folder.setIcon(folder, (ImageView) folderItemView.findViewById(R.id.folder_icon)); return folderItemView; } @Override public void setCursor(ObjectCursor<Folder> cursor) { clear(); if (mParent != null) { add(mParent); } if (cursor != null && cursor.getCount() > 0) { cursor.moveToFirst(); do { add(cursor.getModel()); } while (cursor.moveToNext()); } } @Override public ObjectCursor<Folder> getCursor() { throw new UnsupportedOperationException("drawers don't have hierarchical folders"); } @Override public void setAllFolderListCursor(final ObjectCursor<Folder> cursor) { // Not necessary in HierarchicalFolderListAdapter } @Override public void destroy() { // Do nothing. } } public void rebuildAccountList() { if (!mIsFolderSelectionActivity && mAccountsAdapter != null) { mAccountsAdapter.setAccounts(buildAccountList()); } } protected class AccountsAdapter extends BaseAdapter { private List<DrawerItem> mAccounts; public AccountsAdapter() { mAccounts = new ArrayList<DrawerItem>(); } public void setAccounts(List<DrawerItem> accounts) { mAccounts = accounts; notifyDataSetChanged(); } @Override public int getCount() { return mAccounts.size(); } @Override public Object getItem(int position) { // Is there an attempt made to access outside of the drawer item list? if (position >= mAccounts.size()) { return null; } else { return mAccounts.get(position); } } @Override public long getItemId(int position) { return getItem(position).hashCode(); } @Override public View getView(int position, View convertView, ViewGroup parent) { final DrawerItem item = (DrawerItem) getItem(position); return item.getView(convertView, parent); } } //TS: jian.xu 2015-10-28 EMAIL BUGFIX-834751 ADD-S /** * Check if should show this account's color block. * @param account the account be checked. */ public boolean isAccountColorBlockVisible(Account account) { boolean isVisible = false; if (mCurrentAccount != null && mCurrentAccount.getAccountId().equalsIgnoreCase("Account Id") && !account.getAccountId().equalsIgnoreCase("Account Id")) { isVisible = true; } return isVisible; } //TS: jian.xu 2015-10-28 EMAIL BUGFIX-834751 ADD-E /** * Builds the list of accounts. */ private List<DrawerItem> buildAccountList() { final Account[] allAccounts = getAllAccounts(); final List<DrawerItem> accountList = new ArrayList<DrawerItem>(allAccounts.length); // Add all accounts and then the current account final Uri currentAccountUri = getCurrentAccountUri(); for (final Account account : allAccounts) { final int unreadCount = getUnreadCount(account); //TS: jian.xu 2015-10-28 EMAIL BUGFIX-834751 MOD-S //NOTE: Add a parameter which indicate whether to show account color block. accountList.add( DrawerItem.ofAccount(mActivity, account, unreadCount, currentAccountUri.equals(account.uri), isAccountColorBlockVisible(account), mImagesCache, mContactResolver)); //TS: jian.xu 2015-10-28 EMAIL BUGFIX-834751 MOD-E } if (mCurrentAccount == null) { LogUtils.wtf(LOG_TAG, "buildAccountList() with null current account."); } return accountList; } private Uri getCurrentAccountUri() { return mCurrentAccount == null ? Uri.EMPTY : mCurrentAccount.uri; } protected String getCurrentAccountEmailAddress() { return mCurrentAccount == null ? "" : mCurrentAccount.getEmailAddress(); } protected MergedAdapter<ListAdapter> getMergedAdapter() { return mMergedAdapter; } public Account getCurrentAccount() { return mCurrentAccount; } public ObjectCursor<Folder> getFoldersCursor() { return (mFolderAdapter != null) ? mFolderAdapter.getCursor() : null; } private class FooterAdapter extends BaseAdapter { private final List<FooterItem> mFooterItems = Lists.newArrayList(); private FooterAdapter() { update(); } //TS: zheng.zou 2015-07-31 EMAIL BUGFIX_-1052918 ADD_S @Override public boolean isEnabled(int position) { //the top and bottom item is un-clickable return position > 0 && position < getCount() - 1; } //TS: zheng.zou 2015-07-31 EMAIL BUGFIX_-1052918 ADD_E @Override public int getCount() { return mFooterItems.size(); } @Override public Object getItem(int position) { return mFooterItems.get(position); } @Override public long getItemId(int position) { return position; } /** * @param convertView a view, possibly null, to be recycled. * @param parent the parent hosting this view. * @return a view for the footer item displaying the given text and image. */ @Override public View getView(int position, View convertView, ViewGroup parent) { final ViewGroup footerItemView; if (convertView != null) { footerItemView = (ViewGroup) convertView; } else { footerItemView = (ViewGroup) getActivity().getLayoutInflater().inflate(R.layout.drawer_footer_item, parent, false); } final FooterItem item = (FooterItem) getItem(position); footerItemView.findViewById(R.id.drawer_footer_layout) .setVisibility(item instanceof EmptyItem ? View.GONE : View.VISIBLE); //TS: zheng.zou 2015-07-31 EMAIL BUGFIX_-1052918 ADD footerItemView.findViewById(R.id.top_border) .setVisibility(item.shouldShowTopBorder() ? View.VISIBLE : View.GONE); footerItemView.findViewById(R.id.bottom_margin) .setVisibility(item.shouldIncludeBottomMargin() ? View.VISIBLE : View.GONE); // adjust the text of the footer item final TextView textView = (TextView) footerItemView.findViewById(R.id.drawer_footer_text); textView.setText(item.getTextResourceID()); // adjust the icon of the footer item final ImageView imageView = (ImageView) footerItemView.findViewById(R.id.drawer_footer_image); imageView.setImageResource(item.getImageResourceID()); return footerItemView; } /** * Recomputes the footer drawer items depending on whether the current account * is populated with URIs that navigate to appropriate destinations. */ private void update() { // if the parent activity shows a drawer, these items should participate in that drawer // (if it shows a *pane* they should *not* participate in that pane) if (mIsFolderSelectionActivity) { return; } mFooterItems.clear(); if (mCurrentAccount != null) { //add top divider line fake item mFooterItems.add(new EmptyItem()); //TS: zheng.zou 2015-07-31 EMAIL BUGFIX_-1052918 ADD mFooterItems.add(new SettingsItem()); } if (mCurrentAccount != null && !Utils.isEmpty(mCurrentAccount.helpIntentUri)) { mFooterItems.add(new HelpItem()); } if (!mFooterItems.isEmpty()) { //add bottom spacer fake item mFooterItems.add(new EmptyItem()); //TS: zheng.zou 2015-07-31 EMAIL BUGFIX_-1052918 ADD mFooterItems.get(0).setShowTopBorder(true); mFooterItems.get(mFooterItems.size() - 1).setIncludeBottomMargin(true); } notifyDataSetChanged(); } } /** * Sets the currently selected folder safely. * @param folder the folder to change to. It is an error to pass null here. */ private void setSelectedFolder(Folder folder) { if (folder == null) { mSelectedFolderUri = FolderUri.EMPTY; mCurrentFolderForUnreadCheck = null; LogUtils.e(LOG_TAG, "FolderListFragment.setSelectedFolder(null) called!"); return; } final boolean viewChanged = !FolderItemView.areSameViews(folder, mCurrentFolderForUnreadCheck); // There are two cases in which the folder type is not set by this class. // 1. The activity starts up: from notification/widget/shortcut/launcher. Then we have a // folder but its type was never set. // 2. The user backs into the default inbox. Going 'back' from the conversation list of // any folder will take you to the default inbox for that account. (If you are in the // default inbox already, back exits the app.) // In both these cases, the selected folder type is not set, and must be set. if (mSelectedDrawerItemType == DrawerItem.UNSET || (mCurrentAccount != null && folder.folderUri.equals(mCurrentAccount.settings.defaultInbox))) { mSelectedDrawerItemType = folder.isInbox() ? DrawerItem.FOLDER_INBOX : DrawerItem.FOLDER_OTHER; mSelectedFolderType = folder.type; } mCurrentFolderForUnreadCheck = folder; mSelectedFolderUri = folder.folderUri; if (mFolderAdapter != null && viewChanged) { mFolderAdapter.notifyDataSetChanged(); } } /** * Sets the current account to the one provided here. * @param account the current account to set to. */ private void setSelectedAccount(Account account) { final boolean changed = (account != null) && (mCurrentAccount == null || !mCurrentAccount.uri.equals(account.uri)); mCurrentAccount = account; if (changed) { // Verify that the new account supports sending application feedback updateFooterItems(); // We no longer have proper folder objects. Let the new ones come in mFolderAdapter.setCursor(null); // If currentAccount is different from the one we set, restart the loader. Look at the // comment on {@link AbstractActivityController#restartOptionalLoader} to see why we // don't just do restartLoader. final LoaderManager manager = getLoaderManager(); manager.destroyLoader(FOLDER_LIST_LOADER_ID); manager.restartLoader(FOLDER_LIST_LOADER_ID, Bundle.EMPTY, this); manager.destroyLoader(ALL_FOLDER_LIST_LOADER_ID); manager.restartLoader(ALL_FOLDER_LIST_LOADER_ID, Bundle.EMPTY, this); // An updated cursor causes the entire list to refresh. No need to refresh the list. // But we do need to blank out the current folder, since the account might not be // synced. mSelectedFolderUri = FolderUri.EMPTY; mCurrentFolderForUnreadCheck = null; // also set/update the mini-drawer if (mMiniDrawerEnabled) { //foobar mMiniDrawerView.refresh(); } } else if (account == null) { // This should never happen currently, but is a safeguard against a very incorrect // non-null account -> null account transition. LogUtils.e(LOG_TAG, "FLF.setSelectedAccount(null) called! Destroying existing loader."); final LoaderManager manager = getLoaderManager(); manager.destroyLoader(FOLDER_LIST_LOADER_ID); manager.destroyLoader(ALL_FOLDER_LIST_LOADER_ID); } } private void updateFooterItems() { mFooterAdapter.update(); } /** * Checks if the specified {@link Folder} is a type that we want to exclude from displaying. */ private boolean isFolderTypeExcluded(final Folder folder) { if (mExcludedFolderTypes == null) { return false; } for (final int excludedType : mExcludedFolderTypes) { if (folder.isType(excludedType)) { return true; } } return false; } /** * @return the choice mode to use for the {@link ListView} */ protected int getListViewChoiceMode() { return mAccountController.getFolderListViewChoiceMode(); } /** * The base class of all footer items. Subclasses must fill in the logic of * {@link #doFooterAction()} which contains the behavior when the item is selected. */ private abstract class FooterItem implements View.OnClickListener { private final int mImageResourceID; private final int mTextResourceID; private boolean mShowTopBorder; private boolean mIncludeBottomMargin; private FooterItem(final int imageResourceID, final int textResourceID) { mImageResourceID = imageResourceID; mTextResourceID = textResourceID; } private int getImageResourceID() { return mImageResourceID; } private int getTextResourceID() { return mTextResourceID; } /** * Executes the behavior associated with this footer item.<br> * <br> * WARNING: you probably don't want to call this directly; use * {@link #onClick(View)} instead. This method actually performs the action, and its * execution may be deferred from when the 'click' happens so we can smoothly close the * drawer beforehand. */ abstract void doFooterAction(); @Override public final void onClick(View v) { final DrawerController dc = mActivity.getDrawerController(); if (dc.isDrawerEnabled()) { // close the drawer and defer handling the click until onDrawerClosed mAccountController.closeDrawer(false /* hasNewFolderOrAccount */, null /* nextAccount */, null /* nextFolder */); mDrawerListener.setPendingFooterClick(this); } else { doFooterAction(); } } public boolean shouldShowTopBorder() { return mShowTopBorder; } public void setShowTopBorder(boolean show) { mShowTopBorder = show; } public boolean shouldIncludeBottomMargin() { return mIncludeBottomMargin; } public void setIncludeBottomMargin(boolean include) { mIncludeBottomMargin = include; } // for analytics String getEventLabel() { final StringBuilder sb = new StringBuilder("drawer_footer"); sb.append("/"); sb.append(mActivity.getViewMode().getModeString()); return sb.toString(); } } private class HelpItem extends FooterItem { protected HelpItem() { super(R.drawable.ic_drawer_help, R.string.help_and_feedback); } @Override void doFooterAction() { Analytics.getInstance().sendMenuItemEvent(Analytics.EVENT_CATEGORY_MENU_ITEM, R.id.help_info_menu_item, getEventLabel(), 0); mActivity.showHelp(mCurrentAccount, ViewMode.CONVERSATION_LIST); } } private class SettingsItem extends FooterItem { protected SettingsItem() { super(R.drawable.ic_drawer_settings, R.string.menu_settings); } @Override void doFooterAction() { Analytics.getInstance().sendMenuItemEvent(Analytics.EVENT_CATEGORY_MENU_ITEM, R.id.settings, getEventLabel(), 0); Utils.showSettings(mActivity.getActivityContext(), mCurrentAccount); } } //TS: zheng.zou 2015-07-31 EMAIL BUGFIX_-1052918 ADD_S private class EmptyItem extends FooterItem { protected EmptyItem() { //fake empty resources super(R.drawable.ic_drawer_settings, R.string.menu_settings); } @Override void doFooterAction() { //do nothing } } //TS: zheng.zou 2015-07-31 EMAIL BUGFIX_-1052918 ADD_E /** * Drawer listener for footer functionality to react to drawer state. */ private class DrawerStateListener implements DrawerLayout.DrawerListener { private FooterItem mPendingFooterClick; public void setPendingFooterClick(FooterItem itemClicked) { mPendingFooterClick = itemClicked; } @Override public void onDrawerSlide(View drawerView, float slideOffset) { } @Override public void onDrawerOpened(View drawerView) { } @Override public void onDrawerClosed(View drawerView) { if (mPendingFooterClick != null) { mPendingFooterClick.doFooterAction(); mPendingFooterClick = null; } } @Override public void onDrawerStateChanged(int newState) { } } private class FolderOrAccountListener extends DataSetObserver { @Override public void onChanged() { // First, check if there's a folder to change to if (mNextFolder != null) { mFolderChanger.onFolderSelected(mNextFolder); mNextFolder = null; } // Next, check if there's an account to change to if (mNextAccount != null) { mAccountController.switchToDefaultInboxOrChangeAccount(mNextAccount); mNextAccount = null; } } } @Override public ListAdapter getListAdapter() { // Ensures that we get the adapter with the header views. throw new UnsupportedOperationException( "Use getListView().getAdapter() instead " + "which accounts for any header or footer views."); } }