1b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal/*
2b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal * Copyright (C) 2012 Google Inc.
3b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal * Licensed to The Android Open Source Project.
4b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal *
5b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal * Licensed under the Apache License, Version 2.0 (the "License");
6b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal * you may not use this file except in compliance with the License.
7b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal * You may obtain a copy of the License at
8b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal *
9b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal *      http://www.apache.org/licenses/LICENSE-2.0
10b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal *
11b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal * Unless required by applicable law or agreed to in writing, software
12b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal * distributed under the License is distributed on an "AS IS" BASIS,
13b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal * See the License for the specific language governing permissions and
15b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal * limitations under the License.
16b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal */
17b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
18b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalpackage com.android.mail.ui;
19b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
205cf73b8acfce82fdc8185313b939d0c35271be3cJin Caoimport android.animation.LayoutTransition;
21b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport android.app.Activity;
225b317924c7c764954870501d99bfc57a243f6319Jin Caoimport android.app.Fragment;
2337a20ca39be842b4b4cb54d5b5ed418d49d950cfVikram Aggarwalimport android.app.LoaderManager;
24b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport android.content.res.Resources;
256d8e7fe77c3a3ecfac7bb3bdf0f87f7f89f66b77Mindy Pereiraimport android.database.DataSetObserver;
26b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport android.os.Bundle;
27b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport android.os.Handler;
28f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedyimport android.os.Parcelable;
295b317924c7c764954870501d99bfc57a243f6319Jin Caoimport android.support.annotation.IdRes;
304bb435caccc8ac148ca925813da601d9368d5066Mindy Pereiraimport android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;
31d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Caoimport android.view.KeyEvent;
32b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport android.view.LayoutInflater;
33b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport android.view.View;
34b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport android.view.ViewGroup;
35b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport android.widget.AdapterView;
36b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport android.widget.AdapterView.OnItemLongClickListener;
37b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport android.widget.ListView;
38b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport android.widget.TextView;
39b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
40b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport com.android.mail.ConversationListContext;
41f362695a339276ea1950c274a5bb8516250552e9Marc Blankimport com.android.mail.R;
42761522c1ac7a8fa05acde714f0e26fde47f3415fAndy Huangimport com.android.mail.analytics.Analytics;
4372953f25e00ee8e7b5c7682148430dc82f00a77dJin Caoimport com.android.mail.analytics.AnalyticsTimer;
4480aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwalimport com.android.mail.browse.ConversationCursor;
45d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwalimport com.android.mail.browse.ConversationItemView;
4612fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereiraimport com.android.mail.browse.ConversationItemViewModel;
476681f6b78557850d9ec76a7a8f9ac1ec930d2513Mindy Pereiraimport com.android.mail.browse.ConversationListFooterView;
48642731419abb9037a729114d32d6ddd15af0a406Alice Yangimport com.android.mail.browse.ToggleableItem;
4980aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwalimport com.android.mail.providers.Account;
507c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwalimport com.android.mail.providers.AccountObserver;
51d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwalimport com.android.mail.providers.Conversation;
524f166dead6323b6fd21c76cef66174c6cd4bf11cMindy Pereiraimport com.android.mail.providers.Folder;
5350ff0e50f1a27144e55e4184f48e433439727e7bVikram Aggarwalimport com.android.mail.providers.FolderObserver;
547d81600cbce3cfd366cbff9ecd1b7317ff957221Vikram Aggarwalimport com.android.mail.providers.Settings;
5580aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwalimport com.android.mail.providers.UIProvider;
564e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereiraimport com.android.mail.providers.UIProvider.AccountCapabilities;
575dce146160e82e2087174e5971365a8ac0183b52Scott Kennedyimport com.android.mail.providers.UIProvider.ConversationListIcon;
584e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereiraimport com.android.mail.providers.UIProvider.FolderCapabilities;
59e58222b9421756e11eb24799c1785a090d0793adMindy Pereiraimport com.android.mail.providers.UIProvider.Swipe;
609365a826b46b0e274df88e92534f7d871eef2aa2mindypimport com.android.mail.ui.SwipeableListView.ListItemSwipedListener;
619365a826b46b0e274df88e92534f7d871eef2aa2mindypimport com.android.mail.ui.SwipeableListView.ListItemsRemovedListener;
627c411aafd9ef059f881b7532426e61d1def54a07Andrew Sappersteinimport com.android.mail.ui.SwipeableListView.SwipeListener;
63b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport com.android.mail.ui.ViewMode.ModeChangeListener;
64b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrookimport com.android.mail.utils.LogTag;
65b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwalimport com.android.mail.utils.LogUtils;
66fa131a2ff399fd1d544f759b063268fb4e8a3e70Vikram Aggarwalimport com.android.mail.utils.Utils;
676c570db71b8adc43d7dc2f31e6fa63cf50c1426eAndrew Sappersteinimport com.google.common.collect.ImmutableList;
68b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
69f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereiraimport java.util.Collection;
707c8325de829ca029ce4547e4f0fa266124301367Scott Kennedyimport java.util.List;
71fe06bea4de68a072b0e5a0fc7cefcdfa96143a8fMindy Pereira
72d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Caoimport static android.view.View.OnKeyListener;
73d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao
74b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal/**
75b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal * The conversation list UI component.
76b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal */
775b317924c7c764954870501d99bfc57a243f6319Jin Caopublic final class ConversationListFragment extends Fragment implements
787c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein        OnItemLongClickListener, ModeChangeListener, ListItemSwipedListener, OnRefreshListener,
795b317924c7c764954870501d99bfc57a243f6319Jin Cao        SwipeListener, OnKeyListener, AdapterView.OnItemClickListener {
80649b9ea3653da73fac0c33c8fb0af18baad12ac3Vikram Aggarwal    /** Key used to pass data to {@link ConversationListFragment}. */
81b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    private static final String CONVERSATION_LIST_KEY = "conversation-list";
82649b9ea3653da73fac0c33c8fb0af18baad12ac3Vikram Aggarwal    /** Key used to keep track of the scroll state of the list. */
83b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    private static final String LIST_STATE_KEY = "list-state";
84649b9ea3653da73fac0c33c8fb0af18baad12ac3Vikram Aggarwal
85b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook    private static final String LOG_TAG = LogTag.getLogTag();
86a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    /** Key used to save the ListView choice mode, since ListView doesn't save it automatically! */
87a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    private static final String CHOICE_MODE_KEY = "choice-mode-key";
88b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
89d7a12cd5b5c2639b653d8c671b04844ef02ee01dVikram Aggarwal    // True if we are on a tablet device
90d7a12cd5b5c2639b653d8c671b04844ef02ee01dVikram Aggarwal    private static boolean mTabletDevice;
91d7a12cd5b5c2639b653d8c671b04844ef02ee01dVikram Aggarwal
92aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon    // Delay before displaying the loading view.
930ef5cb2852616e82be9a0f23b431d4983f4513a8Andy Huang    private static int LOADING_DELAY_MS;
944cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon    // Minimum amount of time to keep the loading view displayed.
950ef5cb2852616e82be9a0f23b431d4983f4513a8Andy Huang    private static int MINIMUM_LOADING_DURATION;
96aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon
97b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    /**
989365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * Frequency of update of timestamps. Initialized in
999365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * {@link #onCreate(Bundle)} and final afterwards.
100b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal     */
101b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    private static int TIMESTAMP_UPDATE_INTERVAL = 0;
102b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
10380aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal    private ControllableActivity mActivity;
10480aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal
105b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    // Control state.
106b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    private ConversationListCallbacks mCallbacks;
10780aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal
108b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    private final Handler mHandler = new Handler();
10980aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal
110b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    // The internal view objects.
111f6a6b50c6b45f36b9317e1688f8b87310a67b037Mindy Pereira    private SwipeableListView mListView;
112b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
113af9dc29f589c6ee771db22e3a2923b8bae46aafcJin Cao    private View mSearchHeaderView;
114b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    private TextView mSearchResultCountTextView;
11580aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal
11680aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal    /**
11780aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal     * Current Account being viewed
11880aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal     */
119d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    private Account mAccount;
12080aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal    /**
12130fd47bf1947da5ad813cb957b6cbe569dce563aMindy Pereira     * Current folder being viewed.
12280aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal     */
1234f166dead6323b6fd21c76cef66174c6cd4bf11cMindy Pereira    private Folder mFolder;
12480aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal
125b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    /**
126b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal     * A simple method to update the timestamps of conversations periodically.
127b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal     */
128b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    private Runnable mUpdateTimestampsRunnable = null;
129b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
130b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    private ConversationListContext mViewContext;
131b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
13280aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal    private AnimatedAdapter mListAdapter;
1337d602884ed70d07a42f1adf999fdf51b258f23f9Vikram Aggarwal
1346681f6b78557850d9ec76a7a8f9ac1ec930d2513Mindy Pereira    private ConversationListFooterView mFooterView;
135a44b0eda75782a95cbf16e8d14e8080325028657Andrew Sapperstein    private ConversationListEmptyView mEmptyView;
136aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon    private View mLoadingView;
137c2c9dc14aa184db1f05b8c060b27d97dda5a3ca4Andrew Sapperstein    private ErrorListener mErrorListener;
13850ff0e50f1a27144e55e4184f48e433439727e7bVikram Aggarwal    private FolderObserver mFolderObserver;
139a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    private DataSetObserver mConversationCursorObserver;
140d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal
1414f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal    private ConversationSelectionSet mSelectedSet;
1427c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal    private final AccountObserver mAccountObserver = new AccountObserver() {
1437c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal        @Override
1447c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal        public void onChanged(Account newAccount) {
1457c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal            mAccount = newAccount;
1467c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal            setSwipeAction();
1477c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal        }
1487c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal    };
1499365a826b46b0e274df88e92534f7d871eef2aa2mindyp    private ConversationUpdater mUpdater;
15081a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal    /** Hash of the Conversation Cursor we last obtained from the controller. */
15181a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal    private int mConversationCursorHash;
152343cf45d9913daed981da891538934995d0dd83fJin Cao    // The number of items in the last known ConversationCursor
153343cf45d9913daed981da891538934995d0dd83fJin Cao    private int mConversationCursorLastCount;
15452da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao    // State variable to keep track if we just loaded a new list, used for analytics only
1558bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao    // True if NO DATA has returned, false if we either partially or fully loaded the data
156faa9198087c44f2c46af40c8030b26d69617215dJin Cao    private boolean mInitialCursorLoading;
1574f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal
1585b317924c7c764954870501d99bfc57a243f6319Jin Cao    private @IdRes int mNextFocusLeftId;
1590b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    // Tracks if a onKey event was initiated from the listview (received ACTION_DOWN before
1600b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    // ACTION_UP). If not, the listview only receives ACTION_UP.
1610b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao    private boolean mKeyInitiatedFromList;
1625b317924c7c764954870501d99bfc57a243f6319Jin Cao
1631fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy    /** Duration, in milliseconds, of the CAB mode (peek icon) animation. */
1641fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy    private static long sSelectionModeAnimationDuration = -1;
1651fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy
166856e1313e7ee92319f78ad4fdca18584235c612bJin Cao    // Let's ensure that we are only showing one out of the three views at once
167856e1313e7ee92319f78ad4fdca18584235c612bJin Cao    private void showListView() {
168856e1313e7ee92319f78ad4fdca18584235c612bJin Cao        mListView.setVisibility(View.VISIBLE);
16990c61672da50a00663c54fa2effbbe5febd041c9Jin Cao        mEmptyView.setVisibility(View.INVISIBLE);
17090c61672da50a00663c54fa2effbbe5febd041c9Jin Cao        mLoadingView.setVisibility(View.INVISIBLE);
171856e1313e7ee92319f78ad4fdca18584235c612bJin Cao    }
172856e1313e7ee92319f78ad4fdca18584235c612bJin Cao
173856e1313e7ee92319f78ad4fdca18584235c612bJin Cao    private void showEmptyView() {
1743f386e86d1f211054f9b428b63e8cd8216d3e51bJin Cao        mEmptyView.setupEmptyView(
1753f386e86d1f211054f9b428b63e8cd8216d3e51bJin Cao                mFolder, mViewContext.searchQuery, mListAdapter.getBidiFormatter());
17690c61672da50a00663c54fa2effbbe5febd041c9Jin Cao        mListView.setVisibility(View.INVISIBLE);
177856e1313e7ee92319f78ad4fdca18584235c612bJin Cao        mEmptyView.setVisibility(View.VISIBLE);
17890c61672da50a00663c54fa2effbbe5febd041c9Jin Cao        mLoadingView.setVisibility(View.INVISIBLE);
179856e1313e7ee92319f78ad4fdca18584235c612bJin Cao    }
180856e1313e7ee92319f78ad4fdca18584235c612bJin Cao
181856e1313e7ee92319f78ad4fdca18584235c612bJin Cao    private void showLoadingView() {
18290c61672da50a00663c54fa2effbbe5febd041c9Jin Cao        mListView.setVisibility(View.INVISIBLE);
18390c61672da50a00663c54fa2effbbe5febd041c9Jin Cao        mEmptyView.setVisibility(View.INVISIBLE);
184856e1313e7ee92319f78ad4fdca18584235c612bJin Cao        mLoadingView.setVisibility(View.VISIBLE);
185856e1313e7ee92319f78ad4fdca18584235c612bJin Cao    }
186856e1313e7ee92319f78ad4fdca18584235c612bJin Cao
1874cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon    private final Runnable mLoadingViewRunnable = new FragmentRunnable("LoadingRunnable", this) {
188aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon        @Override
189aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon        public void go() {
19052da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao            if (!isCursorReadyToShow()) {
1914cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon                mCanTakeDownLoadingView = false;
19290c61672da50a00663c54fa2effbbe5febd041c9Jin Cao                showLoadingView();
1934cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon                mHandler.removeCallbacks(mHideLoadingRunnable);
1944cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon                mHandler.postDelayed(mHideLoadingRunnable, MINIMUM_LOADING_DURATION);
195aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon            }
196aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon            mLoadingViewPending = false;
197aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon        }
198aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon    };
199aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon
2004cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon    private final Runnable mHideLoadingRunnable = new FragmentRunnable("CancelLoading", this) {
2014cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon        @Override
2024cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon        public void go() {
2034cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon            mCanTakeDownLoadingView = true;
20452da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao            if (isCursorReadyToShow()) {
2054cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon                hideLoadingViewAndShowContents();
2064cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon            }
2074cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon        }
2084cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon    };
2094cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon
2107f4170af9fdfccf9a289ccfbb6221977bfb14cf4Jin Cao    // Keep track of if we are waiting for the loading view. This variable is also used to check
2117f4170af9fdfccf9a289ccfbb6221977bfb14cf4Jin Cao    // if the cursor corresponding to the current folder loaded (either partially or completely).
212aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon    private boolean mLoadingViewPending;
2134cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon    private boolean mCanTakeDownLoadingView;
214aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon
2156c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal    /**
216f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy     * If <code>true</code>, we have restored (or attempted to restore) the list's scroll position
217f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy     * from when we were last on this conversation list.
218f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy     */
219f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy    private boolean mScrollPositionRestored = false;
220b6910bdf08ce931a1df5d392272014e32a2cf8b6Andrew Sapperstein    private MailSwipeRefreshLayout mSwipeRefreshWidget;
221f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy
222f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy    /**
2239365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * Constructor needs to be public to handle orientation changes and activity
2249365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * lifecycle events.
2256c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal     */
226efc9f120f7f4d9b45c3bf7cb355740956fa5c7c8Paul Westbrook    public ConversationListFragment() {
227d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal        super();
228d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    }
229d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal
2307c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein    @Override
2317c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein    public void onBeginSwipe() {
2327c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein        mSwipeRefreshWidget.setEnabled(false);
2337c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein    }
2347c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein
2357c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein    @Override
2367c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein    public void onEndSwipe() {
2377c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein        mSwipeRefreshWidget.setEnabled(true);
2387c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein    }
2397c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein
240a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    private class ConversationCursorObserver extends DataSetObserver {
24170a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira        @Override
24270a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira        public void onChanged() {
24370a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira            onConversationListStatusUpdated();
24470a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira        }
24570a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira    }
24670a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira
2477d602884ed70d07a42f1adf999fdf51b258f23f9Vikram Aggarwal    /**
2489365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * Creates a new instance of {@link ConversationListFragment}, initialized
2499365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * to display conversation list context.
250b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal     */
251b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    public static ConversationListFragment newInstance(ConversationListContext viewContext) {
252e2be97a58fbae98b95e51735c6468d121e465355Paul Westbrook        final ConversationListFragment fragment = new ConversationListFragment();
253e2be97a58fbae98b95e51735c6468d121e465355Paul Westbrook        final Bundle args = new Bundle(1);
254b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        args.putBundle(CONVERSATION_LIST_KEY, viewContext.toBundle());
255b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        fragment.setArguments(args);
256b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        return fragment;
257b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
258b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
259b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    /**
2609365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * Show the header if the current conversation list is showing search
2619365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * results.
262b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal     */
26368f2e222b4ffccd9f67f02b3a9cfdb3841a7eb43Mindy Pereira    private void updateSearchResultHeader(int count) {
264af9dc29f589c6ee771db22e3a2923b8bae46aafcJin Cao        if (mActivity == null || mSearchHeaderView == null) {
26571a8f297b1d43b9dd0c3b39af820e8a84ab4185fMindy Pereira            return;
26671a8f297b1d43b9dd0c3b39af820e8a84ab4185fMindy Pereira        }
267af9dc29f589c6ee771db22e3a2923b8bae46aafcJin Cao        mSearchResultCountTextView.setText(
268af9dc29f589c6ee771db22e3a2923b8bae46aafcJin Cao                getResources().getString(R.string.search_results_loaded, count));
26980aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal    }
27080aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal
271b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    @Override
2720509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal    public void onActivityCreated(Bundle savedState) {
2730509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        super.onActivityCreated(savedState);
274aaca613cd34f6169e215016195f1953a69df913aMartin Hibdon        mLoadingViewPending = false;
2754cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon        mCanTakeDownLoadingView = true;
2761fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        if (sSelectionModeAnimationDuration < 0) {
2771fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy            sSelectionModeAnimationDuration = getResources().getInteger(
2781fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy                    R.integer.conv_item_view_cab_anim_duration);
2791fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        }
2801fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy
2819365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // Strictly speaking, we get back an android.app.Activity from
2829365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // getActivity. However, the
2839365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // only activity creating a ConversationListContext is a MailActivity
2849365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // which is of type
2859365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // ControllableActivity, so this cast should be safe. If this cast
2869365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // fails, some other
2879365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // activity is creating ConversationListFragments. This activity must be
2889365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // of type
28980aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal        // ControllableActivity.
29080aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal        final Activity activity = getActivity();
2919365a826b46b0e274df88e92534f7d871eef2aa2mindyp        if (!(activity instanceof ControllableActivity)) {
2929365a826b46b0e274df88e92534f7d871eef2aa2mindyp            LogUtils.e(LOG_TAG, "ConversationListFragment expects only a ControllableActivity to"
2939365a826b46b0e274df88e92534f7d871eef2aa2mindyp                    + "create it. Cannot proceed.");
29480aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal        }
29580aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal        mActivity = (ControllableActivity) activity;
2969365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // Since we now have a controllable activity, load the account from it,
2979365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // and register for
2987c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal        // future account changes.
2997c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal        mAccount = mAccountObserver.initialize(mActivity.getAccountController());
3007d602884ed70d07a42f1adf999fdf51b258f23f9Vikram Aggarwal        mCallbacks = mActivity.getListHandler();
301c2c9dc14aa184db1f05b8c060b27d97dda5a3ca4Andrew Sapperstein        mErrorListener = mActivity.getErrorListener();
302278fd226160fa486db17cc7e55deebe5d7a2ae0fMindy Pereira        // Start off with the current state of the folder being viewed.
303af9dc29f589c6ee771db22e3a2923b8bae46aafcJin Cao        final LayoutInflater inflater = LayoutInflater.from(mActivity.getActivityContext());
304af9dc29f589c6ee771db22e3a2923b8bae46aafcJin Cao        mFooterView = (ConversationListFooterView) inflater.inflate(
305af9dc29f589c6ee771db22e3a2923b8bae46aafcJin Cao                R.layout.conversation_list_footer_view, null);
3064969e0c5f8ab837e1b0d29ea56d220e6404e9250Paul Westbrook        mFooterView.setClickListener(mActivity);
30781a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        final ConversationCursor conversationCursor = getConversationListCursor();
30837a20ca39be842b4b4cb54d5b5ed418d49d950cfVikram Aggarwal        final LoaderManager manager = getLoaderManager();
30937a20ca39be842b4b4cb54d5b5ed418d49d950cfVikram Aggarwal
310c5567737cb40ca156ea89f169a23008524ebd1a4Alice Yang        // TODO: These special views are always created, doesn't matter whether they will
311c5567737cb40ca156ea89f169a23008524ebd1a4Alice Yang        // be shown or not, as we add more views this will get more expensive. Given these are
312c5567737cb40ca156ea89f169a23008524ebd1a4Alice Yang        // tips that are only shown once to the user, we should consider creating these on demand.
3137c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy        final ConversationListHelper helper = mActivity.getConversationListHelper();
3142102b2c87a6cb39bc63fb7eff13dda64da56b72eMark Wei        final List<ConversationSpecialItemView> specialItemViews = helper != null ?
3152102b2c87a6cb39bc63fb7eff13dda64da56b72eMark Wei                ImmutableList.copyOf(helper.makeConversationListSpecialViews(
316103319aaed26bce257de55b2fe93d4f78d3c59b9Scott Kennedy                        activity, mActivity, mAccount))
3172102b2c87a6cb39bc63fb7eff13dda64da56b72eMark Wei                : null;
3187c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy        if (specialItemViews != null) {
3197c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy            // Attach to the LoaderManager
3207c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy            for (final ConversationSpecialItemView view : specialItemViews) {
32132ddb84f2d4f53bfc4b53656461068c0c69d6325Scott Kennedy                view.bindFragment(manager, savedState);
3227c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy            }
3237c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy        }
3247c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy
3257c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy        mListAdapter = new AnimatedAdapter(mActivity.getApplicationContext(), conversationCursor,
3266af70772b2037d3527337f220fc053af73d4c81dScott Kennedy                mActivity.getSelectedSet(), mActivity, mListView, specialItemViews);
3276681f6b78557850d9ec76a7a8f9ac1ec930d2513Mindy Pereira        mListAdapter.addFooter(mFooterView);
328eb145d45c17b08ce09131e84b96350925b3db81aJin Cao        // Show search result header only if we are in search mode
329eb145d45c17b08ce09131e84b96350925b3db81aJin Cao        final boolean showSearchHeader = ConversationListContext.isSearchResult(mViewContext);
330eb145d45c17b08ce09131e84b96350925b3db81aJin Cao        if (showSearchHeader) {
331eb145d45c17b08ce09131e84b96350925b3db81aJin Cao            mSearchHeaderView = inflater.inflate(R.layout.search_results_view, null);
332eb145d45c17b08ce09131e84b96350925b3db81aJin Cao            mSearchResultCountTextView = (TextView)
333eb145d45c17b08ce09131e84b96350925b3db81aJin Cao                    mSearchHeaderView.findViewById(R.id.search_result_count_view);
334eb145d45c17b08ce09131e84b96350925b3db81aJin Cao            mListAdapter.addHeader(mSearchHeaderView);
335eb145d45c17b08ce09131e84b96350925b3db81aJin Cao        }
336eb145d45c17b08ce09131e84b96350925b3db81aJin Cao
3376f4a6af149e1f0ee71db356205b810becbc5de2cMindy Pereira        mListView.setAdapter(mListAdapter);
3384f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal        mSelectedSet = mActivity.getSelectedSet();
3394f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal        mListView.setSelectionSet(mSelectedSet);
34037a20ca39be842b4b4cb54d5b5ed418d49d950cfVikram Aggarwal        mListAdapter.setFooterVisibility(false);
34150ff0e50f1a27144e55e4184f48e433439727e7bVikram Aggarwal        mFolderObserver = new FolderObserver(){
34250ff0e50f1a27144e55e4184f48e433439727e7bVikram Aggarwal            @Override
34350ff0e50f1a27144e55e4184f48e433439727e7bVikram Aggarwal            public void onChanged(Folder newFolder) {
34450ff0e50f1a27144e55e4184f48e433439727e7bVikram Aggarwal                onFolderUpdated(newFolder);
34550ff0e50f1a27144e55e4184f48e433439727e7bVikram Aggarwal            }
34650ff0e50f1a27144e55e4184f48e433439727e7bVikram Aggarwal        };
34750ff0e50f1a27144e55e4184f48e433439727e7bVikram Aggarwal        mFolderObserver.initialize(mActivity.getFolderController());
348a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal        mConversationCursorObserver = new ConversationCursorObserver();
3499365a826b46b0e274df88e92534f7d871eef2aa2mindyp        mUpdater = mActivity.getConversationUpdater();
350a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal        mUpdater.registerConversationListObserver(mConversationCursorObserver);
351bcb16b98140a83a4db3c51568d21c839595f73dfVikram Aggarwal        mTabletDevice = Utils.useTabletUI(mActivity.getApplicationContext().getResources());
3529365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // The onViewModeChanged callback doesn't get called when the mode
3539365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // object is created, so
354fa131a2ff399fd1d544f759b063268fb4e8a3e70Vikram Aggarwal        // force setting the mode manually this time around.
355f96ec32ba61bac2a7635f6ef6e04a78cc63cfff8Mindy Pereira        onViewModeChanged(mActivity.getViewMode().getMode());
356b5901be36e247573e8162714e8e006c4dbc35d4aMindy Pereira        mActivity.getViewMode().addListener(this);
3571bc9a559c8e02c45cb57f2dd8ac3cc871bd2961fPaul Westbrook
358b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        if (mActivity.isFinishing()) {
359b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal            // Activity is finishing, just bail.
360b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal            return;
361b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        }
36281a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        mConversationCursorHash = (conversationCursor == null) ? 0 : conversationCursor.hashCode();
36381a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        // Belt and suspenders here; make sure we do any necessary sync of the
36481a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        // ConversationCursor
36581a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        if (conversationCursor != null && conversationCursor.isRefreshReady()) {
36681a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal            conversationCursor.sync();
36781a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        }
36881a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal
3690509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        // On a phone we never highlight a conversation, so the default is to select none.
3700509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        // On a tablet, we highlight a SINGLE conversation in landscape conversation view.
3710509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        int choice = getDefaultChoiceMode(mTabletDevice);
3720509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        if (savedState != null) {
3730509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal            // Restore the choice mode if it was set earlier, or NONE if creating a fresh view.
3740509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal            // Choice mode here represents the current conversation only. CAB mode does not rely on
3750509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal            // the platform: checked state is a local variable {@link ConversationItemView#mChecked}
3760509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal            choice = savedState.getInt(CHOICE_MODE_KEY, choice);
3770509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal            if (savedState.containsKey(LIST_STATE_KEY)) {
3780509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal                // TODO: find a better way to unset the selected item when restoring
3790509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal                mListView.clearChoices();
3800509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal            }
3810509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        }
3820509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        setChoiceMode(choice);
3830509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal
384b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        // Show list and start loading list.
385b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        showList();
3864765c5c90802d0abcf2b05c4967e7dd7d2284f9aMindy Pereira        ToastBarOperation pendingOp = mActivity.getPendingToastOperation();
3874765c5c90802d0abcf2b05c4967e7dd7d2284f9aMindy Pereira        if (pendingOp != null) {
3884765c5c90802d0abcf2b05c4967e7dd7d2284f9aMindy Pereira            // Clear the pending operation
3894765c5c90802d0abcf2b05c4967e7dd7d2284f9aMindy Pereira            mActivity.setPendingToastOperation(null);
3904765c5c90802d0abcf2b05c4967e7dd7d2284f9aMindy Pereira            mActivity.onUndoAvailable(pendingOp);
3914765c5c90802d0abcf2b05c4967e7dd7d2284f9aMindy Pereira        }
392b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
393b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
3940509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal    /**
3950509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal     * Returns the default choice mode for the list based on whether the list is displayed on tablet
3960509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal     * or not.
3970509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal     * @param isTablet
3980509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal     * @return
3990509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal     */
4000509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal    private final static int getDefaultChoiceMode(boolean isTablet) {
4010509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        return isTablet ? ListView.CHOICE_MODE_SINGLE : ListView.CHOICE_MODE_NONE;
4020509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal    }
4030509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal
404967ede63e059810d9d4d425c50111d606014c893Mindy Pereira    public AnimatedAdapter getAnimatedAdapter() {
405967ede63e059810d9d4d425c50111d606014c893Mindy Pereira        return mListAdapter;
406967ede63e059810d9d4d425c50111d606014c893Mindy Pereira    }
407967ede63e059810d9d4d425c50111d606014c893Mindy Pereira
408b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    @Override
409d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal    public void onCreate(Bundle savedState) {
410d247dc966bca1ed303039c2893c54979d448a336Vikram Aggarwal        super.onCreate(savedState);
411b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
412b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        // Initialize fragment constants from resources
4136c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal        final Resources res = getResources();
414b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        TIMESTAMP_UPDATE_INTERVAL = res.getInteger(R.integer.timestamp_update_interval);
4150ef5cb2852616e82be9a0f23b431d4983f4513a8Andy Huang        LOADING_DELAY_MS = res.getInteger(R.integer.conversationview_show_loading_delay);
4160ef5cb2852616e82be9a0f23b431d4983f4513a8Andy Huang        MINIMUM_LOADING_DURATION = res.getInteger(R.integer.conversationview_min_show_loading);
4179365a826b46b0e274df88e92534f7d871eef2aa2mindyp        mUpdateTimestampsRunnable = new Runnable() {
418b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal            @Override
419b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal            public void run() {
420b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal                mListView.invalidateViews();
421b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal                mHandler.postDelayed(mUpdateTimestampsRunnable, TIMESTAMP_UPDATE_INTERVAL);
422b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal            }
423b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        };
424b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
42580aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal        // Get the context from the arguments
4266c51158ad3269f157424e6c7bd488425c98da08fVikram Aggarwal        final Bundle args = getArguments();
427b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        mViewContext = ConversationListContext.forBundle(args.getBundle(CONVERSATION_LIST_KEY));
4283982e236fb2dd36d460df4dbf24e07e8ba55b3bdMindy Pereira        mAccount = mViewContext.account;
42980ecd892733980aad7efb56e6266356a9da13fa8Paul Westbrook
4309a49c9bc441e6ac009cad72cbca523ce84751f65Vikram Aggarwal        setRetainInstance(false);
431b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
432b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
433b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    @Override
4349e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    public String toString() {
4359e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        final String s = super.toString();
4369e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        if (mViewContext == null) {
4379e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang            return s;
4389e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang        }
439c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang        final StringBuilder sb = new StringBuilder(s);
440c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang        sb.setLength(sb.length() - 1);
441c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang        sb.append(" mListAdapter=");
442c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang        sb.append(mListAdapter);
443c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang        sb.append(" folder=");
444c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang        sb.append(mViewContext.folder);
445c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang        sb.append("}");
446c1922a93fb9540d4ff8c28a30d1ae58d3a3d73f9Andy Huang        return sb.toString();
4479e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    }
4489e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang
4499e4ca7993213d296043d20fe9cf4e166b5d595e8Andy Huang    @Override
4509a49c9bc441e6ac009cad72cbca523ce84751f65Vikram Aggarwal    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
451b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        View rootView = inflater.inflate(R.layout.conversation_list, null);
452a44b0eda75782a95cbf16e8d14e8080325028657Andrew Sapperstein        mEmptyView = (ConversationListEmptyView) rootView.findViewById(R.id.empty_view);
4530ef5cb2852616e82be9a0f23b431d4983f4513a8Andy Huang        mLoadingView = rootView.findViewById(R.id.background_view);
454c96efcc4a2f65b11a9b2057320ca828ee80f32ffAndy Huang        mLoadingView.setVisibility(View.GONE);
4550ef5cb2852616e82be9a0f23b431d4983f4513a8Andy Huang        mLoadingView.findViewById(R.id.loading_progress).setVisibility(View.VISIBLE);
4565b317924c7c764954870501d99bfc57a243f6319Jin Cao        mListView = (SwipeableListView) rootView.findViewById(R.id.conversation_list_view);
457b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        mListView.setHeaderDividersEnabled(false);
458b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        mListView.setOnItemLongClickListener(this);
4594e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira        mListView.enableSwipe(mAccount.supportsCapability(AccountCapabilities.UNDO));
4607c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein        mListView.setListItemSwipedListener(this);
4617c411aafd9ef059f881b7532426e61d1def54a07Andrew Sapperstein        mListView.setSwipeListener(this);
462d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao        mListView.setOnKeyListener(this);
4635b317924c7c764954870501d99bfc57a243f6319Jin Cao        mListView.setOnItemClickListener(this);
4645b317924c7c764954870501d99bfc57a243f6319Jin Cao        if (mNextFocusLeftId != 0) {
4655b317924c7c764954870501d99bfc57a243f6319Jin Cao            mListView.setNextFocusLeftId(mNextFocusLeftId);
4665b317924c7c764954870501d99bfc57a243f6319Jin Cao        }
4672af0cf3f538d035c80c7eedacf97362d9a943fa1Marc Blank
4685cf73b8acfce82fdc8185313b939d0c35271be3cJin Cao        // enable animateOnLayout (equivalent of setLayoutTransition) only for >=JB (b/14302062)
4695cf73b8acfce82fdc8185313b939d0c35271be3cJin Cao        if (Utils.isRunningJellybeanOrLater()) {
4705cf73b8acfce82fdc8185313b939d0c35271be3cJin Cao            ((ViewGroup) rootView.findViewById(R.id.conversation_list_parent_frame))
4715cf73b8acfce82fdc8185313b939d0c35271be3cJin Cao                    .setLayoutTransition(new LayoutTransition());
4725cf73b8acfce82fdc8185313b939d0c35271be3cJin Cao        }
4735cf73b8acfce82fdc8185313b939d0c35271be3cJin Cao
474856e1313e7ee92319f78ad4fdca18584235c612bJin Cao        // By default let's show the list view
475856e1313e7ee92319f78ad4fdca18584235c612bJin Cao        showListView();
476856e1313e7ee92319f78ad4fdca18584235c612bJin Cao
4770509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        if (savedState != null && savedState.containsKey(LIST_STATE_KEY)) {
4780509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal            mListView.onRestoreInstanceState(savedState.getParcelable(LIST_STATE_KEY));
4799a49c9bc441e6ac009cad72cbca523ce84751f65Vikram Aggarwal        }
480b6910bdf08ce931a1df5d392272014e32a2cf8b6Andrew Sapperstein        mSwipeRefreshWidget =
481b6910bdf08ce931a1df5d392272014e32a2cf8b6Andrew Sapperstein                (MailSwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_widget);
4824bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira        mSwipeRefreshWidget.setColorScheme(R.color.swipe_refresh_color1,
4834bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira                R.color.swipe_refresh_color2,
4844bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira                R.color.swipe_refresh_color3, R.color.swipe_refresh_color4);
4854bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira        mSwipeRefreshWidget.setOnRefreshListener(this);
486b6910bdf08ce931a1df5d392272014e32a2cf8b6Andrew Sapperstein        mSwipeRefreshWidget.setScrollableChild(mListView);
4870d74a662f6e96b5a21e37978eb6e3b46545e3cbfAlice Yang
488b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        return rootView;
489b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
490b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
491a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    /**
492a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     * Sets the choice mode of the list view
493a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     */
494a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    private final void setChoiceMode(int choiceMode) {
495a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal        mListView.setChoiceMode(choiceMode);
496a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    }
497a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal
498a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    /**
499a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     * Tell the list to select nothing.
500a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     */
501a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    public final void setChoiceNone() {
5020509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        // On a phone, the default choice mode is already none, so nothing to do.
5030509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        if (!mTabletDevice) {
5040509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal            return;
5050509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        }
506b0be3fcbc7b9f99cc6fc0a01e517b0f83e67c1f0Andy Huang        clearChoicesAndActivated();
507a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal        setChoiceMode(ListView.CHOICE_MODE_NONE);
508a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    }
509a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal
510a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    /**
511a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     * Tell the list to get out of selecting none.
512a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     */
513a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    public final void revertChoiceMode() {
5140509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        // On a phone, the default choice mode is always none, so nothing to do.
5150509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        if (!mTabletDevice) {
5160509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal            return;
5170509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        }
5180509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal        setChoiceMode(getDefaultChoiceMode(mTabletDevice));
519a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    }
520a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal
521b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    @Override
5227517e3b61b898a57f19be0671f70d58a82224643Andy Huang    public void onDestroy() {
5237517e3b61b898a57f19be0671f70d58a82224643Andy Huang        super.onDestroy();
5247517e3b61b898a57f19be0671f70d58a82224643Andy Huang    }
5257517e3b61b898a57f19be0671f70d58a82224643Andy Huang
5267517e3b61b898a57f19be0671f70d58a82224643Andy Huang    @Override
527b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    public void onDestroyView() {
5287517e3b61b898a57f19be0671f70d58a82224643Andy Huang
5297c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal        // Clear the list's adapter
5307c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal        mListAdapter.destroy();
531b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        mListView.setAdapter(null);
532b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
5336c570db71b8adc43d7dc2f31e6fa63cf50c1426eAndrew Sapperstein        mActivity.getViewMode().removeListener(this);
53471a8f297b1d43b9dd0c3b39af820e8a84ab4185fMindy Pereira        if (mFolderObserver != null) {
53550ff0e50f1a27144e55e4184f48e433439727e7bVikram Aggarwal            mFolderObserver.unregisterAndDestroy();
53671a8f297b1d43b9dd0c3b39af820e8a84ab4185fMindy Pereira            mFolderObserver = null;
53771a8f297b1d43b9dd0c3b39af820e8a84ab4185fMindy Pereira        }
538a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal        if (mConversationCursorObserver != null) {
539a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal            mUpdater.unregisterConversationListObserver(mConversationCursorObserver);
540a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal            mConversationCursorObserver = null;
54170a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira        }
5427c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal        mAccountObserver.unregisterAndDestroy();
5437c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy        getAnimatedAdapter().cleanup();
544b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        super.onDestroyView();
545b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
546b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
5479730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal    /**
5489365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * There are three binary variables, which determine what we do with a
5499365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * message. checkbEnabled: Whether check boxes are enabled or not (forced
5509365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * true on tablet) cabModeOn: Whether CAB mode is currently on or not.
5519365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * pressType: long or short tap (There is a third possibility: phone or
5529365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * tablet, but they have <em>identical</em> behavior) The matrix of
5539365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * possibilities is:
5549365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * <p>
5559365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * Long tap: Always toggle selection of conversation. If CAB mode is not
5569365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * started, then start it.
5579730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal     * <pre>
5589730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal     *              | Checkboxes | No Checkboxes
5599730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal     *    ----------+------------+---------------
5609730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal     *    CAB mode  |   Select   |     Select
5619730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal     *    List mode |   Select   |     Select
5629730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal     *
5639730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal     * </pre>
5649365a826b46b0e274df88e92534f7d871eef2aa2mindyp     *
5659730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal     * Reference: http://b/issue?id=6392199
5669730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal     * <p>
5679730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal     * {@inheritDoc}
5689730ea0550dacabcd38f0997c02d668058520042Vikram Aggarwal     */
569b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    @Override
570b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
5711e231a6ecd156a47fcb533ebd40d9dfe122cb32eMindy Pereira        // Ignore anything that is not a conversation item. Could be a footer.
5721e231a6ecd156a47fcb533ebd40d9dfe122cb32eMindy Pereira        if (!(view instanceof ConversationItemView)) {
5737c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy            return false;
5741e231a6ecd156a47fcb533ebd40d9dfe122cb32eMindy Pereira        }
575955a766f6c9c30e06dcd504ebc4dd6db510c2fa6Scott Kennedy        return ((ConversationItemView) view).toggleSelectedStateOrBeginDrag();
576b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
577b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
5784f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal    /**
5799365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * See the comment for
5809365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * {@link #onItemLongClick(AdapterView, View, int, long)}.
5819365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * <p>
5829365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * Short tap behavior:
5839365a826b46b0e274df88e92534f7d871eef2aa2mindyp     *
5844f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal     * <pre>
5854f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal     *              | Checkboxes | No Checkboxes
5864f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal     *    ----------+------------+---------------
5879ba7fba9213027c6f763001d32d4c88f0d086388Scott Kennedy     *    CAB mode  |    Peek    |     Select
5884f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal     *    List mode |    Peek    |      Peek
5894f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal     * </pre>
5909365a826b46b0e274df88e92534f7d871eef2aa2mindyp     *
5914f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal     * Reference: http://b/issue?id=6392199
5924f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal     * <p>
5934f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal     * {@inheritDoc}
5944f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal     */
595b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    @Override
5965b317924c7c764954870501d99bfc57a243f6319Jin Cao    public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
597d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao        onListItemSelected(view, position);
598d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao    }
599d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao
600d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao    private void onListItemSelected(View view, int position) {
6010e8dc84326ad3e6d146a203538665f98dd98f688Scott Kennedy        if (view instanceof ToggleableItem) {
6025dce146160e82e2087174e5971365a8ac0183b52Scott Kennedy            final boolean showSenderImage =
6035dce146160e82e2087174e5971365a8ac0183b52Scott Kennedy                    (mAccount.settings.convListIcon == ConversationListIcon.SENDER_IMAGE);
604042a530b2296487fa5899a3e871214ac4a47e3d8Andy Huang            final boolean inCabMode = !mSelectedSet.isEmpty();
605042a530b2296487fa5899a3e871214ac4a47e3d8Andy Huang            if (!showSenderImage && inCabMode) {
60637a20ca39be842b4b4cb54d5b5ed418d49d950cfVikram Aggarwal                ((ToggleableItem) view).toggleSelectedState();
60737a20ca39be842b4b4cb54d5b5ed418d49d950cfVikram Aggarwal            } else {
608042a530b2296487fa5899a3e871214ac4a47e3d8Andy Huang                if (inCabMode) {
609042a530b2296487fa5899a3e871214ac4a47e3d8Andy Huang                    // this is a peek.
610042a530b2296487fa5899a3e871214ac4a47e3d8Andy Huang                    Analytics.getInstance().sendEvent("peek", null, null, mSelectedSet.size());
611042a530b2296487fa5899a3e871214ac4a47e3d8Andy Huang                }
61272953f25e00ee8e7b5c7682148430dc82f00a77dJin Cao                AnalyticsTimer.getInstance().trackStart(AnalyticsTimer.OPEN_CONV_VIEW_FROM_LIST);
6138afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy                viewConversation(position);
61437a20ca39be842b4b4cb54d5b5ed418d49d950cfVikram Aggarwal            }
6154f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal        } else {
61637a20ca39be842b4b4cb54d5b5ed418d49d950cfVikram Aggarwal            // Ignore anything that is not a conversation item. Could be a footer.
61737a20ca39be842b4b4cb54d5b5ed418d49d950cfVikram Aggarwal            // If we are using a keyboard, the highlighted item is the parent;
61837a20ca39be842b4b4cb54d5b5ed418d49d950cfVikram Aggarwal            // otherwise, this is a direct call from the ConverationItemView
61937a20ca39be842b4b4cb54d5b5ed418d49d950cfVikram Aggarwal            return;
6204f1cc0ba5e7bb4083ca9542d6fcb58aae9ba7611Vikram Aggarwal        }
6219365a826b46b0e274df88e92534f7d871eef2aa2mindyp        // When a new list item is clicked, commit any existing leave behind
6228694fe9a90b0f8d6f6dc9b8d3a33c49366454f99mindyp        // items. Wait until we have opened the desired conversation to cause
6238694fe9a90b0f8d6f6dc9b8d3a33c49366454f99mindyp        // any position changes.
624bcb16b98140a83a4db3c51568d21c839595f73dfVikram Aggarwal        commitDestructiveActions(Utils.useTabletUI(mActivity.getActivityContext().getResources()));
625b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
626b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
627b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    @Override
628d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao    public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
629d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao        SwipeableListView list = (SwipeableListView) view;
630d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao        // Don't need to handle ENTER because it's auto-handled as a "click".
631d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao        if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
632d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao            if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
6330b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                if (mKeyInitiatedFromList) {
6340b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                    onListItemSelected(list.getSelectedView(), list.getSelectedItemPosition());
6350b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                }
6360b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                mKeyInitiatedFromList = false;
6370b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao            } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
6380b69338a45faa422ccba8faf64c9816c55d33e4aJin Cao                mKeyInitiatedFromList = true;
639d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao            }
640d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao            return true;
641d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao        } else if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
642d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao            if (keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
643d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao                final int position = list.getSelectedItemPosition();
6447ee90b37599cb6717782d40a6d085849918c29dfJin Cao                final Object item = getAnimatedAdapter().getItem(position);
6457ee90b37599cb6717782d40a6d085849918c29dfJin Cao                if (item != null && item instanceof ConversationCursor) {
6467ee90b37599cb6717782d40a6d085849918c29dfJin Cao                    final Conversation conv = ((ConversationCursor) item).getConversation();
647d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao                    mCallbacks.onConversationFocused(conv);
648d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao                }
649d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao            }
650d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao        }
651d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao        return false;
652d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao    }
653d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao
654d23f6d1cd1dd8248cba755bf91c5355a31fbb885Jin Cao    @Override
65508a079c3d2857e365736432b2691187767eb116fScott Kennedy    public void onResume() {
65608a079c3d2857e365736432b2691187767eb116fScott Kennedy        super.onResume();
65708a079c3d2857e365736432b2691187767eb116fScott Kennedy
65852da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao        if (!isCursorReadyToShow()) {
65952da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao            // If the cursor got reset, let's reset the analytics state variable and show the list
66052da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao            // view since we are waiting for load again
661fa934276f5d1d5432ac96abd7308c3dd7f3e6d4bJin Cao            mInitialCursorLoading = true;
662fa934276f5d1d5432ac96abd7308c3dd7f3e6d4bJin Cao            showListView();
663fa934276f5d1d5432ac96abd7308c3dd7f3e6d4bJin Cao        }
664fa934276f5d1d5432ac96abd7308c3dd7f3e6d4bJin Cao
66552da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao        final ConversationCursor conversationCursor = getConversationListCursor();
66608a079c3d2857e365736432b2691187767eb116fScott Kennedy        if (conversationCursor != null) {
66708a079c3d2857e365736432b2691187767eb116fScott Kennedy            conversationCursor.handleNotificationActions();
668f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy
669f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy            restoreLastScrolledPosition();
67008a079c3d2857e365736432b2691187767eb116fScott Kennedy        }
6711fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy
6721fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        mSelectedSet.addObserver(mConversationSetObserver);
67308a079c3d2857e365736432b2691187767eb116fScott Kennedy    }
67408a079c3d2857e365736432b2691187767eb116fScott Kennedy
67508a079c3d2857e365736432b2691187767eb116fScott Kennedy    @Override
676b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    public void onPause() {
677b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        super.onPause();
6781fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy
6791fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        mSelectedSet.removeObserver(mConversationSetObserver);
680f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy
681f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy        saveLastScrolledPosition();
682b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
683b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
684b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    @Override
685b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    public void onSaveInstanceState(Bundle outState) {
686b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        super.onSaveInstanceState(outState);
687b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        if (mListView != null) {
688b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal            outState.putParcelable(LIST_STATE_KEY, mListView.onSaveInstanceState());
689a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal            outState.putInt(CHOICE_MODE_KEY, mListView.getChoiceMode());
690b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        }
69132ddb84f2d4f53bfc4b53656461068c0c69d6325Scott Kennedy
69232ddb84f2d4f53bfc4b53656461068c0c69d6325Scott Kennedy        if (mListAdapter != null) {
69332ddb84f2d4f53bfc4b53656461068c0c69d6325Scott Kennedy            mListAdapter.saveSpecialItemInstanceState(outState);
69432ddb84f2d4f53bfc4b53656461068c0c69d6325Scott Kennedy        }
695b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
696b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
697b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    @Override
698b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    public void onStart() {
699b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        super.onStart();
700b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        mHandler.postDelayed(mUpdateTimestampsRunnable, TIMESTAMP_UPDATE_INTERVAL);
701a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang        Analytics.getInstance().sendView("ConversationListFragment");
702b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
703b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
704b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    @Override
705b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    public void onStop() {
706b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        super.onStop();
707b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        mHandler.removeCallbacks(mUpdateTimestampsRunnable);
708b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
709b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
71080aeac5356b5a14a5ef2aff8f0f72a464c9a3cb4Vikram Aggarwal    @Override
711fa131a2ff399fd1d544f759b063268fb4e8a3e70Vikram Aggarwal    public void onViewModeChanged(int newMode) {
712fa131a2ff399fd1d544f759b063268fb4e8a3e70Vikram Aggarwal        if (mTabletDevice) {
713b0be3fcbc7b9f99cc6fc0a01e517b0f83e67c1f0Andy Huang            if (ViewMode.isListMode(newMode)) {
7140509bba9a80de474a24368e1f3baebd3cd32cd5eVikram Aggarwal                // There are no selected conversations when in conversation list mode.
715b0be3fcbc7b9f99cc6fc0a01e517b0f83e67c1f0Andy Huang                clearChoicesAndActivated();
716b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal            }
717b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal        }
718b5901be36e247573e8162714e8e006c4dbc35d4aMindy Pereira        if (mFooterView != null) {
719b5901be36e247573e8162714e8e006c4dbc35d4aMindy Pereira            mFooterView.onViewModeChanged(newMode);
720b5901be36e247573e8162714e8e006c4dbc35d4aMindy Pereira        }
7215b317924c7c764954870501d99bfc57a243f6319Jin Cao
7225b317924c7c764954870501d99bfc57a243f6319Jin Cao        // Set default navigation
7235b317924c7c764954870501d99bfc57a243f6319Jin Cao        if (ViewMode.isListMode(newMode)) {
7245b317924c7c764954870501d99bfc57a243f6319Jin Cao            mListView.setNextFocusRightId(R.id.conversation_list_view);
7255b317924c7c764954870501d99bfc57a243f6319Jin Cao            mListView.requestFocus();
7265b317924c7c764954870501d99bfc57a243f6319Jin Cao        } else if (ViewMode.isConversationMode(newMode)) {
7275b317924c7c764954870501d99bfc57a243f6319Jin Cao            // This would only happen in two_pane
7285b317924c7c764954870501d99bfc57a243f6319Jin Cao            mListView.setNextFocusRightId(R.id.conversation_pager);
7295b317924c7c764954870501d99bfc57a243f6319Jin Cao        }
730b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
731b5901be36e247573e8162714e8e006c4dbc35d4aMindy Pereira
73248ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang    public boolean isAnimating() {
73348ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang        final AnimatedAdapter adapter = getAnimatedAdapter();
734f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        if (adapter != null && adapter.isAnimating()) {
735f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang            return true;
736f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        }
737f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        final boolean isScrolling = (mListView != null && mListView.isScrolling());
738f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        if (isScrolling) {
739f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang            LogUtils.i(LOG_TAG, "CLF.isAnimating=true due to scrolling");
740f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        }
741f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        return isScrolling;
74248ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang    }
74348ccbc53ef90bf6420f831f63e6243008e02a346Andy Huang
744b0be3fcbc7b9f99cc6fc0a01e517b0f83e67c1f0Andy Huang    private void clearChoicesAndActivated() {
745b0be3fcbc7b9f99cc6fc0a01e517b0f83e67c1f0Andy Huang        final int currentSelected = mListView.getCheckedItemPosition();
746b0be3fcbc7b9f99cc6fc0a01e517b0f83e67c1f0Andy Huang        if (currentSelected != ListView.INVALID_POSITION) {
747f0aebd32d750d6e108c8f9b551b1760682123ebbAndy Huang            mListView.setItemChecked(mListView.getCheckedItemPosition(), false);
748b0be3fcbc7b9f99cc6fc0a01e517b0f83e67c1f0Andy Huang        }
749b0be3fcbc7b9f99cc6fc0a01e517b0f83e67c1f0Andy Huang    }
750b0be3fcbc7b9f99cc6fc0a01e517b0f83e67c1f0Andy Huang
751b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    /**
7529365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * Handles a request to show a new conversation list, either from a search
7539365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * query or for viewing a folder. This will initiate a data load, and hence
7549365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * must be called on the UI thread.
755b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal     */
756b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    private void showList() {
757faa9198087c44f2c46af40c8030b26d69617215dJin Cao        mInitialCursorLoading = true;
7587591d2fc98c4308567a1d8822b41b48133227b25Andy Huang        onFolderUpdated(mActivity.getFolderController().getFolder());
759ca8ca2def56c0af5aa8237d19815d591ffc020e5mindyp        onConversationListStatusUpdated();
760a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang
761a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang        // try to get an order-of-magnitude sense for message count within folders
762a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang        // (N.B. this count currently isn't working for search folders, since their counts stream
763a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang        // in over time in pieces.)
764a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang        final Folder f = mViewContext.folder;
765a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang        if (f != null) {
766a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang            final long countLog;
767a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang            if (f.totalCount > 0) {
768a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang                countLog = (long) Math.log10(f.totalCount);
769a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang            } else {
770a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang                countLog = 0;
771a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang            }
772a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang            Analytics.getInstance().sendEvent("view_folder", f.getTypeDescription(),
773a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang                    Long.toString(countLog), f.totalCount);
774a90c33b7db2f47cd1e8129dd8c262e9e3e329f71Andy Huang        }
775b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal    }
776b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal
7778afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy    /**
7788afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy     * View the message at the given position.
7798afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy     *
7808afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy     * @param position The position of the conversation in the list (as opposed to its position
7818afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy     *        in the cursor)
7828afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy     */
7838afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy    private void viewConversation(final int position) {
7848afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy        LogUtils.d(LOG_TAG, "ConversationListFragment.viewConversation(%d)", position);
7851fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy
7868afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy        final ConversationCursor cursor =
7878afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy                (ConversationCursor) getAnimatedAdapter().getItem(position);
78897b10963491a88254e84924c208844c4a3fdbec4Andy Huang
7898afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy        if (cursor == null) {
7908afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy            LogUtils.e(LOG_TAG,
7918afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy                    "unable to open conv at cursor pos=%s cursor=%s getPositionOffset=%s",
7928afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy                    position, cursor, getAnimatedAdapter().getPositionOffset(position));
7938afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy            return;
7941fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        }
7951fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy
7968afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy        final Conversation conv = cursor.getConversation();
7978afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy        /*
7988afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy         * The cursor position may be different than the position method parameter because of
7998afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy         * special views in the list.
8008afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy         */
8018afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy        conv.position = cursor.getPosition();
8028afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy        setSelected(conv.position, true);
8038afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy        mCallbacks.onConversationSelected(conv, false /* inLoaderCallbacks */);
8048afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy    }
8051fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy
806a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal    /**
807a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     * Sets the selected conversation to the position given here.
8080d74a662f6e96b5a21e37978eb6e3b46545e3cbfAlice Yang     * @param cursorPosition The position of the conversation in the cursor (as opposed to
8090d74a662f6e96b5a21e37978eb6e3b46545e3cbfAlice Yang     * in the list)
810a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     * @param different if the currently selected conversation is different from the one provided
811a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     * here.  This is a difference in conversations, not a difference in positions. For example, a
812a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     * conversation at position 2 can move to position 4 as a result of new mail.
813a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     */
8147c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy    public void setSelected(final int cursorPosition, boolean different) {
815a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal        if (mListView.getChoiceMode() == ListView.CHOICE_MODE_NONE) {
816a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal            return;
817a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal        }
8187c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy
8197c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy        final int position =
8207c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy                cursorPosition + getAnimatedAdapter().getPositionOffset(cursorPosition);
8217c8325de829ca029ce4547e4f0fa266124301367Scott Kennedy
822e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy        setRawSelected(position, different);
823e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy    }
824e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy
825e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy    /**
826e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy     * Sets the selected conversation to the position given here.
827e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy     * @param position The position of the item in the list
828e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy     * @param different if the currently selected conversation is different from the one provided
829e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy     * here.  This is a difference in conversations, not a difference in positions. For example, a
830e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy     * conversation at position 2 can move to position 4 as a result of new mail.
831e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy     */
832e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy    public void setRawSelected(final int position, final boolean different) {
833e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy        if (mListView.getChoiceMode() == ListView.CHOICE_MODE_NONE) {
834e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy            return;
835e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy        }
836e0d01fb7379225a0f058999d9da9050f2fcc6945Scott Kennedy
837e21f88695fca2fde6d3636ab505ca28f74c8eb42mindyp        if (different) {
838e21f88695fca2fde6d3636ab505ca28f74c8eb42mindyp            mListView.smoothScrollToPosition(position);
839e21f88695fca2fde6d3636ab505ca28f74c8eb42mindyp        }
840e21f88695fca2fde6d3636ab505ca28f74c8eb42mindyp        mListView.setItemChecked(position, true);
841e21f88695fca2fde6d3636ab505ca28f74c8eb42mindyp    }
842e21f88695fca2fde6d3636ab505ca28f74c8eb42mindyp
8433d7ca9d2aefd20dc115f20532c1e19fdb60f7d68Vikram Aggarwal    /**
844a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     * Returns the cursor associated with the conversation list.
845a91d00b4de3092b41af5f36436d3b49fe4586f64Vikram Aggarwal     * @return
8463d7ca9d2aefd20dc115f20532c1e19fdb60f7d68Vikram Aggarwal     */
847967ede63e059810d9d4d425c50111d606014c893Mindy Pereira    private ConversationCursor getConversationListCursor() {
848967ede63e059810d9d4d425c50111d606014c893Mindy Pereira        return mCallbacks != null ? mCallbacks.getConversationListCursor() : null;
849fe06bea4de68a072b0e5a0fc7cefcdfa96143a8fMindy Pereira    }
850fe06bea4de68a072b0e5a0fc7cefcdfa96143a8fMindy Pereira
851fe06bea4de68a072b0e5a0fc7cefcdfa96143a8fMindy Pereira    /**
8529365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * Request a refresh of the list. No sync is carried out and none is
8539365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * promised.
85454452ae7a9cdb607ed1db5e74077b5ebd95c46a2Vikram Aggarwal     */
85554452ae7a9cdb607ed1db5e74077b5ebd95c46a2Vikram Aggarwal    public void requestListRefresh() {
85654452ae7a9cdb607ed1db5e74077b5ebd95c46a2Vikram Aggarwal        mListAdapter.notifyDataSetChanged();
85754452ae7a9cdb607ed1db5e74077b5ebd95c46a2Vikram Aggarwal    }
85854452ae7a9cdb607ed1db5e74077b5ebd95c46a2Vikram Aggarwal
85975daee52f08c9bf66652b9bddf42cfbd1ca71987Vikram Aggarwal    /**
8607f602f7a64f176894ccb7942a6642f22584c3894Vikram Aggarwal     * Change the UI to delete the conversations provided and then call the
8619365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * {@link DestructiveAction} provided here <b>after</b> the UI has been
8629365a826b46b0e274df88e92534f7d871eef2aa2mindyp     * updated.
8637f602f7a64f176894ccb7942a6642f22584c3894Vikram Aggarwal     * @param conversations
86475daee52f08c9bf66652b9bddf42cfbd1ca71987Vikram Aggarwal     * @param action
86575daee52f08c9bf66652b9bddf42cfbd1ca71987Vikram Aggarwal     */
866a8e4318bb9921e2ec6045c5f7187a4b78c55fe10Vikram Aggarwal    public void requestDelete(int actionId, final Collection<Conversation> conversations,
867669947bf67a3d0eed377b18099bb873604ad5009Vikram Aggarwal            final DestructiveAction action) {
868acf6039a23382f18c35f6b487d90d53cb67b5858Mindy Pereira        for (Conversation conv : conversations) {
869acf6039a23382f18c35f6b487d90d53cb67b5858Mindy Pereira            conv.localDeleteOnUpdate = true;
870acf6039a23382f18c35f6b487d90d53cb67b5858Mindy Pereira        }
871a8e4318bb9921e2ec6045c5f7187a4b78c55fe10Vikram Aggarwal        final ListItemsRemovedListener listener = new ListItemsRemovedListener() {
8729365a826b46b0e274df88e92534f7d871eef2aa2mindyp            @Override
8739365a826b46b0e274df88e92534f7d871eef2aa2mindyp            public void onListItemsRemoved() {
8749365a826b46b0e274df88e92534f7d871eef2aa2mindyp                action.performAction();
8759365a826b46b0e274df88e92534f7d871eef2aa2mindyp            }
876a8e4318bb9921e2ec6045c5f7187a4b78c55fe10Vikram Aggarwal        };
8775b317924c7c764954870501d99bfc57a243f6319Jin Cao        if (mListView.getSwipeAction() == actionId) {
8785b317924c7c764954870501d99bfc57a243f6319Jin Cao            if (!mListView.destroyItems(conversations, listener)) {
879cec3e0b9c90173e0bdb2d072046b38714fc9186ePaul Westbrook                // The listView failed to destroy the items, perform the action manually
880cec3e0b9c90173e0bdb2d072046b38714fc9186ePaul Westbrook                LogUtils.e(LOG_TAG, "ConversationListFragment.requestDelete: " +
881cec3e0b9c90173e0bdb2d072046b38714fc9186ePaul Westbrook                        "listView failed to destroy items.");
882cec3e0b9c90173e0bdb2d072046b38714fc9186ePaul Westbrook                action.performAction();
883cec3e0b9c90173e0bdb2d072046b38714fc9186ePaul Westbrook            }
884a8e4318bb9921e2ec6045c5f7187a4b78c55fe10Vikram Aggarwal            return;
885a8e4318bb9921e2ec6045c5f7187a4b78c55fe10Vikram Aggarwal        }
886a8e4318bb9921e2ec6045c5f7187a4b78c55fe10Vikram Aggarwal        // Delete the local delete items (all for now) and when done,
887a8e4318bb9921e2ec6045c5f7187a4b78c55fe10Vikram Aggarwal        // update...
888a8e4318bb9921e2ec6045c5f7187a4b78c55fe10Vikram Aggarwal        mListAdapter.delete(conversations, listener);
889acf6039a23382f18c35f6b487d90d53cb67b5858Mindy Pereira    }
890a3911aafc876fc21cdbc5a997ecea5c1f293df5dMindy Pereira
89111dd5effd79b8078b24bcdb4160e8e12a17317a5Mindy Pereira    public void onFolderUpdated(Folder folder) {
89252da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao        if (!isCursorReadyToShow()) {
893faa9198087c44f2c46af40c8030b26d69617215dJin Cao            // Wait a bit before showing either the empty or loading view. If the messages are
894faa9198087c44f2c46af40c8030b26d69617215dJin Cao            // actually local, it's disorienting to see this appear on every folder transition.
895faa9198087c44f2c46af40c8030b26d69617215dJin Cao            // If they aren't, then it will likely take more than 200 milliseconds to load, and
896faa9198087c44f2c46af40c8030b26d69617215dJin Cao            // then we'll see the loading view.
897faa9198087c44f2c46af40c8030b26d69617215dJin Cao            if (!mLoadingViewPending) {
898faa9198087c44f2c46af40c8030b26d69617215dJin Cao                mHandler.postDelayed(mLoadingViewRunnable, LOADING_DELAY_MS);
899faa9198087c44f2c46af40c8030b26d69617215dJin Cao                mLoadingViewPending = true;
900faa9198087c44f2c46af40c8030b26d69617215dJin Cao            }
901faa9198087c44f2c46af40c8030b26d69617215dJin Cao        }
902faa9198087c44f2c46af40c8030b26d69617215dJin Cao
903dc8963bb37926496e14df8e53e3e5a87a777d9b4Mindy Pereira        mFolder = folder;
90406642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira        setSwipeAction();
9054bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira
9064bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira        // Update enabled state of swipe to refresh.
9074bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira        mSwipeRefreshWidget.setEnabled(!ConversationListContext.isSearchResult(mViewContext));
9084bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira
90982cea4896518e35bf8509b221bb46ae5ac610699Mindy Pereira        if (mFolder == null) {
91082cea4896518e35bf8509b221bb46ae5ac610699Mindy Pereira            return;
91182cea4896518e35bf8509b221bb46ae5ac610699Mindy Pereira        }
9124584a0d83e160444f931cb565185a2eea39b1683Mindy Pereira        mListAdapter.setFolder(mFolder);
91370a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira        mFooterView.setFolder(mFolder);
91441b9e8f7bea47bbcae71b9ae81c3608a00a90e70Vikram Aggarwal        if (!mFolder.wasSyncSuccessful()) {
91570a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira            mErrorListener.onError(mFolder, false);
91670a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira        }
917c01f5d9455f3482b2d72a179733d5854c8acccd0Paul Westbrook
918b95d5611e508abae1f3bacaf46a6649e21007a96Jin Cao        // Update the sync status bar with sync results if needed
919b95d5611e508abae1f3bacaf46a6649e21007a96Jin Cao        checkSyncStatus();
920c01f5d9455f3482b2d72a179733d5854c8acccd0Paul Westbrook
92112fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira        // Blow away conversation items cache.
92212fe37aa24c313fd8192dede0770dadf0ec23359Mindy Pereira        ConversationItemViewModel.onFolderUpdated(mFolder);
92370a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira    }
92470a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira
92581a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal    /**
92681a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal     * Updates the footer visibility and updates the conversation cursor
92781a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal     */
92870a70c92bca006af84177d025c32deee3aa6da3fMindy Pereira    public void onConversationListStatusUpdated() {
929c01f5d9455f3482b2d72a179733d5854c8acccd0Paul Westbrook        // Also change the cursor here.
930c01f5d9455f3482b2d72a179733d5854c8acccd0Paul Westbrook        onCursorUpdated();
931faa9198087c44f2c46af40c8030b26d69617215dJin Cao
93252da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao        if (isCursorReadyToShow() && mCanTakeDownLoadingView) {
933faa9198087c44f2c46af40c8030b26d69617215dJin Cao            hideLoadingViewAndShowContents();
934faa9198087c44f2c46af40c8030b26d69617215dJin Cao        }
935c01f5d9455f3482b2d72a179733d5854c8acccd0Paul Westbrook    }
936c01f5d9455f3482b2d72a179733d5854c8acccd0Paul Westbrook
9374cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon    private void hideLoadingViewAndShowContents() {
9384cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon        final ConversationCursor cursor = getConversationListCursor();
9394cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon        final boolean showFooter = mFooterView.updateStatus(cursor);
940b95d5611e508abae1f3bacaf46a6649e21007a96Jin Cao        // Update the sync status bar with sync results if needed
941b95d5611e508abae1f3bacaf46a6649e21007a96Jin Cao        checkSyncStatus();
9424cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon        mListAdapter.setFooterVisibility(showFooter);
9434cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon        mLoadingViewPending = false;
9444cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon        mHandler.removeCallbacks(mLoadingViewRunnable);
945ee6766a2c6938dbb4c975d93732d7033eecd90a5Jin Cao
946ee6766a2c6938dbb4c975d93732d7033eecd90a5Jin Cao        // Even though cursor might be empty, the list adapter might have teasers/footers.
947ee6766a2c6938dbb4c975d93732d7033eecd90a5Jin Cao        // So we check the list adapter count if the cursor is fully/partially loaded.
948ee6766a2c6938dbb4c975d93732d7033eecd90a5Jin Cao        if (cursor != null && ConversationCursor.isCursorReadyToShow(cursor) &&
949ee6766a2c6938dbb4c975d93732d7033eecd90a5Jin Cao                mListAdapter.getCount() == 0) {
950ee6766a2c6938dbb4c975d93732d7033eecd90a5Jin Cao            showEmptyView();
951ee6766a2c6938dbb4c975d93732d7033eecd90a5Jin Cao        } else {
952ee6766a2c6938dbb4c975d93732d7033eecd90a5Jin Cao            showListView();
953ee6766a2c6938dbb4c975d93732d7033eecd90a5Jin Cao        }
9544cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon    }
9554cdc4857b1b42659158b585648a58c3260a9536fMartin Hibdon
95606642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira    private void setSwipeAction() {
9577c401b7896910c00e6234e8774aab0be45740d32Vikram Aggarwal        int swipeSetting = Settings.getSwipeSetting(mAccount.settings);
958e58222b9421756e11eb24799c1785a090d0793adMindy Pereira        if (swipeSetting == Swipe.DISABLED
959b807337282fb19d1a38cdbe092a2f472b0e30321Mindy Pereira                || !mAccount.supportsCapability(AccountCapabilities.UNDO)
960b807337282fb19d1a38cdbe092a2f472b0e30321Mindy Pereira                || (mFolder != null && mFolder.isTrash())) {
961e58222b9421756e11eb24799c1785a090d0793adMindy Pereira            mListView.enableSwipe(false);
962e58222b9421756e11eb24799c1785a090d0793adMindy Pereira        } else {
9633b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy            final int action;
9647f98d0da720da81d497046c038ffeca6d56f2a08Rohan Shah            mListView.enableSwipe(true);
965d4e02e4072a34c0207841298ddcb4f081a94973aJin Cao            if (mFolder == null) {
96601f30509c4c8ad0d13b7635447b7f023c7a0cab7Mindy Pereira                action = R.id.remove_folder;
9674e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira            } else {
9684e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                switch (swipeSetting) {
969d4e02e4072a34c0207841298ddcb4f081a94973aJin Cao                    // Try to respect user's setting as best as we can and default to doing nothing
970d4e02e4072a34c0207841298ddcb4f081a94973aJin Cao                    case Swipe.DELETE:
971512821c11d89d49908f3cfdee0b582601f500f3dJin Cao                        // Delete in Outbox means discard failed message and put it in draft
972512821c11d89d49908f3cfdee0b582601f500f3dJin Cao                        if (mFolder.isType(UIProvider.FolderType.OUTBOX)) {
973512821c11d89d49908f3cfdee0b582601f500f3dJin Cao                            action = R.id.discard_outbox;
974512821c11d89d49908f3cfdee0b582601f500f3dJin Cao                        } else {
975512821c11d89d49908f3cfdee0b582601f500f3dJin Cao                            action = R.id.delete;
976512821c11d89d49908f3cfdee0b582601f500f3dJin Cao                        }
977d4e02e4072a34c0207841298ddcb4f081a94973aJin Cao                        break;
9784e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                    case Swipe.ARCHIVE:
979d4e02e4072a34c0207841298ddcb4f081a94973aJin Cao                        // Special case spam since it shouldn't remove spam folder label on swipe
980d4e02e4072a34c0207841298ddcb4f081a94973aJin Cao                        if (mAccount.supportsCapability(AccountCapabilities.ARCHIVE)
981d4e02e4072a34c0207841298ddcb4f081a94973aJin Cao                                && !mFolder.isSpam()) {
9824e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                            if (mFolder.supportsCapability(FolderCapabilities.ARCHIVE)) {
9834e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                                action = R.id.archive;
9844e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                                break;
9854e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                            } else if (mFolder.supportsCapability
9864e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                                    (FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)) {
98701f30509c4c8ad0d13b7635447b7f023c7a0cab7Mindy Pereira                                action = R.id.remove_folder;
9884e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                                break;
9894e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                            }
9904e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                        }
9913b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy
9923b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                        /*
9933b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                         * If we get here, we don't support archive, on either the account or the
9943432b636fbcf7d6cc551b9705ed4dd88ae2b4f49Jin Cao                         * folder, so we want to fall through to swipe doing nothing
9953b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                         */
9963b965d78774a42358ce6bbdcc43b4c8df130a60eScott Kennedy                        //$FALL-THROUGH$
9974e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                    default:
9983432b636fbcf7d6cc551b9705ed4dd88ae2b4f49Jin Cao                        mListView.enableSwipe(false);
9993432b636fbcf7d6cc551b9705ed4dd88ae2b4f49Jin Cao                        action = 0; // Use default value so setSwipeAction essentially has no effect
10004e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                        break;
10014e812f49c5bbba50a60d062d624403952f8d5487Mindy Pereira                }
1002e58222b9421756e11eb24799c1785a090d0793adMindy Pereira            }
1003e58222b9421756e11eb24799c1785a090d0793adMindy Pereira            mListView.setSwipeAction(action);
1004e58222b9421756e11eb24799c1785a090d0793adMindy Pereira        }
10056126d72ae2769bd39451872f45781cadb5b90515Mark Wei        mListView.setCurrentAccount(mAccount);
100606642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira        mListView.setCurrentFolder(mFolder);
100706642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira    }
100806642fab1bf4ab95b5dd97a65b262845cf60c865Mindy Pereira
100917f373ecf6961112f77a7516ab1bbcb4fcdf2884Vikram Aggarwal    /**
101017f373ecf6961112f77a7516ab1bbcb4fcdf2884Vikram Aggarwal     * Changes the conversation cursor in the list and sets selected position if none is set.
101117f373ecf6961112f77a7516ab1bbcb4fcdf2884Vikram Aggarwal     */
101217f373ecf6961112f77a7516ab1bbcb4fcdf2884Vikram Aggarwal    private void onCursorUpdated() {
101317f373ecf6961112f77a7516ab1bbcb4fcdf2884Vikram Aggarwal        if (mCallbacks == null || mListAdapter == null) {
101417f373ecf6961112f77a7516ab1bbcb4fcdf2884Vikram Aggarwal            return;
101517f373ecf6961112f77a7516ab1bbcb4fcdf2884Vikram Aggarwal        }
101681a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        // Check against the previous cursor here and see if they are the same. If they are, then
101781a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        // do a notifyDataSetChanged.
101881a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        final ConversationCursor newCursor = mCallbacks.getConversationListCursor();
1019f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy
1020f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy        if (newCursor == null && mListAdapter.getCursor() != null) {
1021f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy            // We're losing our cursor, so save our scroll position
1022f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy            saveLastScrolledPosition();
1023f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy        }
1024f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy
102581a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        mListAdapter.swapCursor(newCursor);
102681a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        // When the conversation cursor is *updated*, we get back the same instance. In that
102781a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        // situation, CursorAdapter.swapCursor() silently returns, without forcing a
102881a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        // notifyDataSetChanged(). So let's force a call to notifyDataSetChanged, since an updated
102981a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        // cursor means that the dataset has changed.
103081a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        final int newCursorHash = (newCursor == null) ? 0 : newCursor.hashCode();
103181a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        if (mConversationCursorHash == newCursorHash && mConversationCursorHash != 0) {
103281a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal            mListAdapter.notifyDataSetChanged();
103381a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        }
103481a4f08b1657bb297cd977e3df92660fab05a1abVikram Aggarwal        mConversationCursorHash = newCursorHash;
1035b1e21487e18503cc110e1cf8726b835e048ab1c1Scott Kennedy
10368bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao        updateAnalyticsData(newCursor);
1037780fbf7b56959bbdd2b3346b2f92cabedd84b50ePaul Westbrook        if (newCursor != null) {
1038780fbf7b56959bbdd2b3346b2f92cabedd84b50ePaul Westbrook            final int newCursorCount = newCursor.getCount();
1039780fbf7b56959bbdd2b3346b2f92cabedd84b50ePaul Westbrook            updateSearchResultHeader(newCursorCount);
1040780fbf7b56959bbdd2b3346b2f92cabedd84b50ePaul Westbrook            if (newCursorCount > 0) {
1041780fbf7b56959bbdd2b3346b2f92cabedd84b50ePaul Westbrook                newCursor.markContentsSeen();
1042780fbf7b56959bbdd2b3346b2f92cabedd84b50ePaul Westbrook                restoreLastScrolledPosition();
1043780fbf7b56959bbdd2b3346b2f92cabedd84b50ePaul Westbrook            }
1044b1e21487e18503cc110e1cf8726b835e048ab1c1Scott Kennedy        }
1045b1e21487e18503cc110e1cf8726b835e048ab1c1Scott Kennedy
104617f373ecf6961112f77a7516ab1bbcb4fcdf2884Vikram Aggarwal        // If a current conversation is available, and none is selected in the list, then ask
104717f373ecf6961112f77a7516ab1bbcb4fcdf2884Vikram Aggarwal        // the list to select the current conversation.
104817f373ecf6961112f77a7516ab1bbcb4fcdf2884Vikram Aggarwal        final Conversation conv = mCallbacks.getCurrentConversation();
1049f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy        if (conv != null) {
1050f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy            if (mListView.getChoiceMode() != ListView.CHOICE_MODE_NONE
1051f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy                    && mListView.getCheckedItemPosition() == -1) {
1052f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy                setSelected(conv.position, true);
1053f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy            }
1054967ede63e059810d9d4d425c50111d606014c893Mindy Pereira        }
105521ab490d4919985d611bc36ae2d8aea86a911499Mindy Pereira    }
10561e2573bbba613fb700e8fa01c58c9382c0027b5eMindy Pereira
1057c6adce3cf6887c4c6dd5005724565702751843d0mindyp    public void commitDestructiveActions(boolean animate) {
10588937bf1552a86853efc798a4d8df34c01115cdfdMindy Pereira        if (mListView != null) {
1059c6adce3cf6887c4c6dd5005724565702751843d0mindyp            mListView.commitDestructiveActions(animate);
10607d81600cbce3cfd366cbff9ecd1b7317ff957221Vikram Aggarwal
10617d81600cbce3cfd366cbff9ecd1b7317ff957221Vikram Aggarwal        }
10627d81600cbce3cfd366cbff9ecd1b7317ff957221Vikram Aggarwal    }
10637d81600cbce3cfd366cbff9ecd1b7317ff957221Vikram Aggarwal
10649365a826b46b0e274df88e92534f7d871eef2aa2mindyp    @Override
10659365a826b46b0e274df88e92534f7d871eef2aa2mindyp    public void onListItemSwiped(Collection<Conversation> conversations) {
10669365a826b46b0e274df88e92534f7d871eef2aa2mindyp        mUpdater.showNextConversation(conversations);
10679365a826b46b0e274df88e92534f7d871eef2aa2mindyp    }
10680d74a662f6e96b5a21e37978eb6e3b46545e3cbfAlice Yang
1069486e63e5bc7a88455e6fcb83d9cb251df203f727Alice Yang    private void checkSyncStatus() {
107076d20657374ba16e2d4fdc8319057068cfcefcf6Alice Yang        if (mFolder != null && mFolder.isSyncInProgress()) {
107103752f3a42e2cfb54c3873c12739982cb16b58d5Alice Yang            LogUtils.d(LOG_TAG, "CLF.checkSyncStatus still syncing");
1072486e63e5bc7a88455e6fcb83d9cb251df203f727Alice Yang            // Still syncing, ignore
10730d74a662f6e96b5a21e37978eb6e3b46545e3cbfAlice Yang        } else {
1074486e63e5bc7a88455e6fcb83d9cb251df203f727Alice Yang            // Finished syncing:
107503752f3a42e2cfb54c3873c12739982cb16b58d5Alice Yang            LogUtils.d(LOG_TAG, "CLF.checkSyncStatus done syncing");
10764bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira            mSwipeRefreshWidget.setRefreshing(false);
10770d74a662f6e96b5a21e37978eb6e3b46545e3cbfAlice Yang        }
10780d74a662f6e96b5a21e37978eb6e3b46545e3cbfAlice Yang    }
10790d74a662f6e96b5a21e37978eb6e3b46545e3cbfAlice Yang
1080486e63e5bc7a88455e6fcb83d9cb251df203f727Alice Yang    /**
1081486e63e5bc7a88455e6fcb83d9cb251df203f727Alice Yang     * Displays the indefinite progress bar indicating a sync is in progress.  This
1082486e63e5bc7a88455e6fcb83d9cb251df203f727Alice Yang     * should only be called if user manually requested a sync, and not for background syncs.
1083486e63e5bc7a88455e6fcb83d9cb251df203f727Alice Yang     */
1084486e63e5bc7a88455e6fcb83d9cb251df203f727Alice Yang    protected void showSyncStatusBar() {
10854bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira        mSwipeRefreshWidget.setRefreshing(true);
10860d74a662f6e96b5a21e37978eb6e3b46545e3cbfAlice Yang    }
1087d4f2287a529ae743955700f318f60bb5b6c02bc0Rohan Shah
1088d4f2287a529ae743955700f318f60bb5b6c02bc0Rohan Shah    /**
1089d4f2287a529ae743955700f318f60bb5b6c02bc0Rohan Shah     * Clears all items in the list.
1090d4f2287a529ae743955700f318f60bb5b6c02bc0Rohan Shah     */
1091d4f2287a529ae743955700f318f60bb5b6c02bc0Rohan Shah    public void clear() {
1092d4f2287a529ae743955700f318f60bb5b6c02bc0Rohan Shah        mListView.setAdapter(null);
1093d4f2287a529ae743955700f318f60bb5b6c02bc0Rohan Shah    }
10941fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy
10951fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy    private final ConversationSetObserver mConversationSetObserver = new ConversationSetObserver() {
10961fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        @Override
10971fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        public void onSetPopulated(final ConversationSelectionSet set) {
10984bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira            // Disable the swipe to refresh widget.
10994bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira            mSwipeRefreshWidget.setEnabled(false);
11001fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        }
11011fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy
11021fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        @Override
11031fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        public void onSetEmpty() {
11044bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira            mSwipeRefreshWidget.setEnabled(true);
11051fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        }
11061fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy
11071fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        @Override
11081fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        public void onSetChanged(final ConversationSelectionSet set) {
11098afccad8b03701577bb4b734a0f4fe76f6cab487Scott Kennedy            // Do nothing
11101fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy        }
11111fea6a3cffcc8c4afc3d877e5dc57d29f9665942Scott Kennedy    };
1112f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy
1113f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy    private void saveLastScrolledPosition() {
1114f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy        if (mListAdapter.getCursor() == null) {
1115f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy            // If you save your scroll position in an empty list, you're gonna have a bad time
1116f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy            return;
1117f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy        }
1118f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy
1119f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy        final Parcelable savedState = mListView.onSaveInstanceState();
1120f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy
1121f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy        mActivity.getListHandler().setConversationListScrollPosition(
1122f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy                mFolder.conversationListUri.toString(), savedState);
1123f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy    }
1124f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy
1125f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy    private void restoreLastScrolledPosition() {
1126f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy        // Scroll to our previous position, if necessary
1127599c3cfaeff6ff519b08467a3fcd96fb33c0e1e1Scott Kennedy        if (!mScrollPositionRestored && mFolder != null) {
1128599c3cfaeff6ff519b08467a3fcd96fb33c0e1e1Scott Kennedy            final String key = mFolder.conversationListUri.toString();
1129f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy            final Parcelable savedState = mActivity.getListHandler()
1130599c3cfaeff6ff519b08467a3fcd96fb33c0e1e1Scott Kennedy                    .getConversationListScrollPosition(key);
1131f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy            if (savedState != null) {
1132f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy                mListView.onRestoreInstanceState(savedState);
1133f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy            }
1134f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy            mScrollPositionRestored = true;
1135f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy        }
1136f77806ed36ab49861a5ea13a3ab34e5a3a21c022Scott Kennedy    }
11374bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira
11384bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira    /* (non-Javadoc)
11394bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira     * @see android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener#onRefresh()
11404bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira     */
11414bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira    @Override
11424bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira    public void onRefresh() {
11434bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira        Analytics.getInstance().sendEvent(Analytics.EVENT_CATEGORY_MENU_ITEM, "swipe_refresh", null,
11444bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira                0);
11454bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira
11464bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira        // This will call back to showSyncStatusBar():
11474bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira        mActivity.getFolderController().requestFolderRefresh();
1148f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang
1149f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        // Clear list adapter state out of an abundance of caution.
1150f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        // There is a class of bugs where an animation that should have finished doesn't (maybe
1151f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        // it didn't start, or it didn't finish), and the list gets stuck pretty much forever.
1152f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        // Clearing the state here is in line with user expectation for 'refresh'.
1153f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        getAnimatedAdapter().clearAnimationState();
1154f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        // possibly act on the now-cleared state
1155f21787ad2ac7d68e3620ffa3ae6e8e7fadf0bd54Andy Huang        mActivity.onAnimationEnd(mListAdapter);
11564bb435caccc8ac148ca925813da601d9368d5066Mindy Pereira    }
1157343cf45d9913daed981da891538934995d0dd83fJin Cao
1158343cf45d9913daed981da891538934995d0dd83fJin Cao    /**
11598bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao     * Extracted function that handles Analytics state and logging updates for each new cursor
11608bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao     * @param newCursor the new cursor pointer
1161343cf45d9913daed981da891538934995d0dd83fJin Cao     */
1162343cf45d9913daed981da891538934995d0dd83fJin Cao    private void updateAnalyticsData(ConversationCursor newCursor) {
11638bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao        if (newCursor != null) {
11648bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao            // Check if the initial data returned yet
11658bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao            if (mInitialCursorLoading) {
11668bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                // This marks the very first time the cursor with the data the user sees returned.
11678bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                // We either have a cursor in LOADING state with cursor's count > 0, OR the cursor
11688bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                // completed loading.
11698bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                // Use this point to log the appropriate timing information that depends on when
11708bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                // the conversation list view finishes loading
117152da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao                if (isCursorReadyToShow()) {
11728bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                    if (newCursor.getCount() == 0) {
11738bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                        Analytics.getInstance().sendEvent("empty_state", "post_label_change",
11748bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                                mFolder.getTypeDescription(), 0);
11758bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                    }
11768bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                    AnalyticsTimer.getInstance().logDuration(AnalyticsTimer.COLD_START_LAUNCHER,
11778bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                            true /* isDestructive */, "cold_start_to_list", "from_launcher", null);
11788bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                    // Don't need null checks because the activity, controller, and folder cannot
11798bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                    // be null in this case
11808bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                    if (mActivity.getFolderController().getFolder().isSearch()) {
11818bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                        AnalyticsTimer.getInstance().logDuration(AnalyticsTimer.SEARCH_TO_LIST,
11828bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                                true /* isDestructive */, "search_to_list", null, null);
11838bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                    }
11848bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao
11858bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                    mInitialCursorLoading = false;
11868bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                }
11878bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao            } else {
11888bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                // Log the appropriate events that happen after the initial cursor is loaded
11898bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                if (newCursor.getCount() == 0 && mConversationCursorLastCount > 0) {
11908bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                    Analytics.getInstance().sendEvent("empty_state", "post_delete",
11918bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                            mFolder.getTypeDescription(), 0);
11928bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao                }
1193343cf45d9913daed981da891538934995d0dd83fJin Cao            }
1194faa9198087c44f2c46af40c8030b26d69617215dJin Cao
11958bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao            // We save the count here because for folders that are empty, multiple successful
11968bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao            // cursor loads will occur with size of 0. Thus we don't want to emit any false
11978bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao            // positive post_delete events.
11988bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao            mConversationCursorLastCount = newCursor.getCount();
11998bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao        } else {
12008bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao            mConversationCursorLastCount = 0;
12018bf9c21a6e89e812621fe2d46c799651f9677784Jin Cao        }
1202343cf45d9913daed981da891538934995d0dd83fJin Cao    }
1203fa934276f5d1d5432ac96abd7308c3dd7f3e6d4bJin Cao
1204fa934276f5d1d5432ac96abd7308c3dd7f3e6d4bJin Cao    /**
120552da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao     * Helper function to determine if the current cursor is ready to populate the UI
1206bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao     * Since we extracted the functionality into a static function in ConversationCursor,
1207bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao     * this function remains for the sole purpose of readability.
1208fa934276f5d1d5432ac96abd7308c3dd7f3e6d4bJin Cao     * @return
1209fa934276f5d1d5432ac96abd7308c3dd7f3e6d4bJin Cao     */
121052da8ba2b62cd3479be1ed9d447cb3b64d388395Jin Cao    private boolean isCursorReadyToShow() {
1211bc29ed3a5fe96ac144d5157150551febfb4d1355Jin Cao        return ConversationCursor.isCursorReadyToShow(getConversationListCursor());
1212fa934276f5d1d5432ac96abd7308c3dd7f3e6d4bJin Cao    }
12135b317924c7c764954870501d99bfc57a243f6319Jin Cao
12145b317924c7c764954870501d99bfc57a243f6319Jin Cao    public ListView getListView() {
12155b317924c7c764954870501d99bfc57a243f6319Jin Cao        return mListView;
12165b317924c7c764954870501d99bfc57a243f6319Jin Cao    }
12175b317924c7c764954870501d99bfc57a243f6319Jin Cao
12185b317924c7c764954870501d99bfc57a243f6319Jin Cao    public void setNextFocusLeftId(@IdRes int id) {
12195b317924c7c764954870501d99bfc57a243f6319Jin Cao        mNextFocusLeftId = id;
12205b317924c7c764954870501d99bfc57a243f6319Jin Cao        if (mListView != null) {
12215b317924c7c764954870501d99bfc57a243f6319Jin Cao            mListView.setNextFocusLeftId(mNextFocusLeftId);
12225b317924c7c764954870501d99bfc57a243f6319Jin Cao        }
12235b317924c7c764954870501d99bfc57a243f6319Jin Cao    }
1224b9e1a353c6a173a2885642dbcc1939f3281f29f7Vikram Aggarwal}
1225