MailboxListFragment.java revision 56034cab2022fe78ccbf4635617b1a54b4cc16b8
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email.activity;
18
19import com.android.email.Email;
20import com.android.email.RefreshManager;
21import com.android.email.Utility;
22
23import android.app.Activity;
24import android.app.ListFragment;
25import android.app.LoaderManager.LoaderCallbacks;
26import android.content.Loader;
27import android.database.Cursor;
28import android.os.Bundle;
29import android.util.Log;
30import android.view.View;
31import android.widget.AdapterView;
32import android.widget.AdapterView.OnItemClickListener;
33import android.widget.ListView;
34
35import java.security.InvalidParameterException;
36
37/**
38 * This fragment presents a list of mailboxes for a given account.  The "API" includes the
39 * following elements which must be provided by the host Activity.
40 *
41 *  - call bindActivityInfo() to provide the account ID and set callbacks
42 *  - provide callbacks for onOpen and onRefresh
43 *  - pass-through implementations of onCreateContextMenu() and onContextItemSelected() (temporary)
44 *
45 * TODO Restoring ListView state -- don't do this when changing accounts
46 */
47public class MailboxListFragment extends ListFragment implements OnItemClickListener {
48    private static final String BUNDLE_KEY_SELECTED_MAILBOX_ID
49            = "MailboxListFragment.state.selected_mailbox_id";
50    private static final String BUNDLE_LIST_STATE = "MailboxListFragment.state.listState";
51    private static final int LOADER_ID_MAILBOX_LIST = 1;
52
53    private long mLastLoadedAccountId = -1;
54    private long mAccountId = -1;
55    private long mSelectedMailboxId = -1;
56
57    // UI Support
58    private Activity mActivity;
59    private MailboxesAdapter mListAdapter;
60    private Callback mCallback = EmptyCallback.INSTANCE;
61
62    private ListView mListView;
63
64    private boolean mOpenRequested;
65    private boolean mResumed;
66
67    private Utility.ListStateSaver mSavedListState;
68
69    /**
70     * Callback interface that owning activities must implement
71     */
72    public interface Callback {
73        public void onMailboxSelected(long accountId, long mailboxId);
74    }
75
76    private static class EmptyCallback implements Callback {
77        public static final Callback INSTANCE = new EmptyCallback();
78        @Override
79        public void onMailboxSelected(long accountId, long mailboxId) {
80        }
81    }
82
83    /**
84     * Called to do initial creation of a fragment.  This is called after
85     * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
86     */
87    @Override
88    public void onCreate(Bundle savedInstanceState) {
89        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
90            Log.d(Email.LOG_TAG, "MailboxListFragment onCreate");
91        }
92        super.onCreate(savedInstanceState);
93
94        mActivity = getActivity();
95        mListAdapter = new MailboxesAdapter(mActivity, MailboxesAdapter.MODE_NORMAL);
96        if (savedInstanceState != null) {
97            restoreInstanceState(savedInstanceState);
98        }
99    }
100
101    @Override
102    public void onActivityCreated(Bundle savedInstanceState) {
103        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
104            Log.d(Email.LOG_TAG, "MailboxListFragment onActivityCreated");
105        }
106        super.onActivityCreated(savedInstanceState);
107
108        mListView = getListView();
109        mListView.setOnItemClickListener(this);
110        mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
111        registerForContextMenu(mListView);
112    }
113
114    public void setCallback(Callback callback) {
115        mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
116    }
117
118    /**
119     * @param accountId the account we're looking at
120     */
121    public void openMailboxes(long accountId) {
122        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
123            Log.d(Email.LOG_TAG, "MailboxListFragment openMailboxes");
124        }
125        if (accountId == -1) {
126            throw new InvalidParameterException();
127        }
128        if (mAccountId == accountId) {
129            return;
130        }
131        mOpenRequested = true;
132        mAccountId = accountId;
133        if (mResumed) {
134            startLoading();
135        }
136    }
137
138    public void setSelectedMailbox(long mailboxId) {
139        mSelectedMailboxId = mailboxId;
140        if (mResumed) {
141            highlightSelectedMailbox(true);
142        }
143    }
144
145    /**
146     * Called when the Fragment is visible to the user.
147     */
148    @Override
149    public void onStart() {
150        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
151            Log.d(Email.LOG_TAG, "MailboxListFragment onStart");
152        }
153        super.onStart();
154    }
155
156    /**
157     * Called when the fragment is visible to the user and actively running.
158     */
159    @Override
160    public void onResume() {
161        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
162            Log.d(Email.LOG_TAG, "MailboxListFragment onResume");
163        }
164        super.onResume();
165        mResumed = true;
166
167        // If we're recovering from the stopped state, we don't have to reload.
168        // (when mOpenRequested = false)
169        if (mAccountId != -1 && mOpenRequested) {
170            startLoading();
171        }
172    }
173
174    @Override
175    public void onPause() {
176        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
177            Log.d(Email.LOG_TAG, "MailboxListFragment onPause");
178        }
179        mResumed = false;
180        super.onPause();
181        mSavedListState = new Utility.ListStateSaver(getListView());
182    }
183
184    /**
185     * Called when the Fragment is no longer started.
186     */
187    @Override
188    public void onStop() {
189        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
190            Log.d(Email.LOG_TAG, "MailboxListFragment onStop");
191        }
192        super.onStop();
193    }
194
195    /**
196     * Called when the fragment is no longer in use.
197     */
198    @Override
199    public void onDestroy() {
200        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
201            Log.d(Email.LOG_TAG, "MailboxListFragment onDestroy");
202        }
203        super.onDestroy();
204    }
205
206    @Override
207    public void onSaveInstanceState(Bundle outState) {
208        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
209            Log.d(Email.LOG_TAG, "MailboxListFragment onSaveInstanceState");
210        }
211        super.onSaveInstanceState(outState);
212        outState.putLong(BUNDLE_KEY_SELECTED_MAILBOX_ID, mSelectedMailboxId);
213        outState.putParcelable(BUNDLE_LIST_STATE, new Utility.ListStateSaver(getListView()));
214    }
215
216    private void restoreInstanceState(Bundle savedInstanceState) {
217        mSelectedMailboxId = savedInstanceState.getLong(BUNDLE_KEY_SELECTED_MAILBOX_ID);
218        mSavedListState = savedInstanceState.getParcelable(BUNDLE_LIST_STATE);
219    }
220
221    private void startLoading() {
222        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
223            Log.d(Email.LOG_TAG, "MailboxListFragment startLoading");
224        }
225        mOpenRequested = false;
226        // Clear the list.  (ListFragment will show the "Loading" animation)
227        setListShown(false);
228
229        // If we've already loaded for a different account, discard the previous result and
230        // start loading again.
231        // We don't want to use restartLoader(), because if the Loader is retained, we *do* want to
232        // reuse the previous result.
233        // Also, when changing accounts, we don't preserve scroll position.
234        boolean accountChanging = false;
235        if ((mLastLoadedAccountId != -1) && (mLastLoadedAccountId != mAccountId)) {
236            accountChanging = true;
237            getLoaderManager().stopLoader(LOADER_ID_MAILBOX_LIST);
238        }
239        getLoaderManager().initLoader(LOADER_ID_MAILBOX_LIST, null,
240                new MailboxListLoaderCallbacks(accountChanging));
241    }
242
243    private class MailboxListLoaderCallbacks implements LoaderCallbacks<Cursor> {
244        private boolean mAccountChanging;
245
246        public MailboxListLoaderCallbacks(boolean accountChanging) {
247            mAccountChanging = accountChanging;
248        }
249
250        @Override
251        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
252            if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
253                Log.d(Email.LOG_TAG, "MailboxListFragment onCreateLoader");
254            }
255            return MailboxesAdapter.createLoader(getActivity(), mAccountId,
256                    MailboxesAdapter.MODE_NORMAL);
257        }
258
259        @Override
260        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
261            if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
262                Log.d(Email.LOG_TAG, "MailboxListFragment onLoadFinished");
263            }
264            mLastLoadedAccountId = mAccountId;
265
266            // Save list view state (primarily scroll position)
267            final ListView lv = getListView();
268            final Utility.ListStateSaver lss;
269            if (mAccountChanging) {
270                lss = null; // Don't preserve list state
271            } else if (mSavedListState != null) {
272                lss = mSavedListState;
273                mSavedListState = null;
274            } else {
275                lss = new Utility.ListStateSaver(lv);
276            }
277
278            if (cursor.getCount() == 0) {
279                // If there's no row, don't set it to the ListView.
280                // Instead use setListShown(false) to make ListFragment show progress icon.
281                mListAdapter.changeCursor(null);
282                setListShown(false);
283            } else {
284                // Set the adapter.
285                mListAdapter.changeCursor(cursor);
286                setListAdapter(mListAdapter);
287                setListShown(true);
288
289                // We want to make selection visible only when account is changing..
290                // i.e. Refresh caused by content changed events shouldn't scroll the list.
291                highlightSelectedMailbox(mAccountChanging);
292            }
293
294            // Restore the state
295            if (lss != null) {
296                lss.restore(lv);
297            }
298
299            // Clear this for next reload triggered by content changed events.
300            mAccountChanging = false;
301        }
302    }
303
304    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
305        mCallback.onMailboxSelected(mAccountId, id);
306    }
307
308    public void onRefresh() {
309        if (mAccountId != -1) {
310            RefreshManager.getInstance(getActivity()).refreshMailboxList(mAccountId);
311        }
312    }
313
314    /**
315     * Highlight the selected mailbox.
316     */
317    private void highlightSelectedMailbox(boolean ensureSelectionVisible) {
318        if (mSelectedMailboxId == -1) {
319            // No mailbox selected
320            mListView.clearChoices();
321            return;
322        }
323        final int count = mListView.getCount();
324        for (int i = 0; i < count; i++) {
325            if (mListView.getItemIdAtPosition(i) != mSelectedMailboxId) {
326                continue;
327            }
328            mListView.setItemChecked(i, true);
329            if (ensureSelectionVisible) {
330                Log.w(Email.LOG_TAG, "MailboxListFragment -- ensure visible");
331                Utility.listViewSmoothScrollToPosition(getActivity(), mListView, i);
332            }
333            break;
334        }
335    }
336}
337