MailboxListFragment.java revision 9b5001a34c82ea59b57a790fc221eec3af89e254
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 private RefreshManager mRefreshManager; 58 59 // UI Support 60 private Activity mActivity; 61 private MailboxesAdapter mListAdapter; 62 private Callback mCallback = EmptyCallback.INSTANCE; 63 64 private ListView mListView; 65 66 private boolean mOpenRequested; 67 private boolean mResumed; 68 69 private Utility.ListStateSaver mSavedListState; 70 71 /** 72 * Callback interface that owning activities must implement 73 */ 74 public interface Callback { 75 public void onMailboxSelected(long accountId, long mailboxId); 76 } 77 78 private static class EmptyCallback implements Callback { 79 public static final Callback INSTANCE = new EmptyCallback(); 80 @Override 81 public void onMailboxSelected(long accountId, long mailboxId) { 82 } 83 } 84 85 /** 86 * Called to do initial creation of a fragment. This is called after 87 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 88 */ 89 @Override 90 public void onCreate(Bundle savedInstanceState) { 91 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 92 Log.d(Email.LOG_TAG, "MailboxListFragment onCreate"); 93 } 94 super.onCreate(savedInstanceState); 95 96 mActivity = getActivity(); 97 mRefreshManager = RefreshManager.getInstance(mActivity); 98 mListAdapter = new MailboxesAdapter(mActivity, MailboxesAdapter.MODE_NORMAL); 99 if (savedInstanceState != null) { 100 restoreInstanceState(savedInstanceState); 101 } 102 } 103 104 @Override 105 public void onActivityCreated(Bundle savedInstanceState) { 106 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 107 Log.d(Email.LOG_TAG, "MailboxListFragment onActivityCreated"); 108 } 109 super.onActivityCreated(savedInstanceState); 110 111 mListView = getListView(); 112 mListView.setOnItemClickListener(this); 113 mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 114 registerForContextMenu(mListView); 115 } 116 117 public void setCallback(Callback callback) { 118 mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback; 119 } 120 121 /** 122 * @param accountId the account we're looking at 123 */ 124 public void openMailboxes(long accountId) { 125 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 126 Log.d(Email.LOG_TAG, "MailboxListFragment openMailboxes"); 127 } 128 if (accountId == -1) { 129 throw new InvalidParameterException(); 130 } 131 if (mAccountId == accountId) { 132 return; 133 } 134 mOpenRequested = true; 135 mAccountId = accountId; 136 if (mResumed) { 137 startLoading(); 138 } 139 } 140 141 public void setSelectedMailbox(long mailboxId) { 142 mSelectedMailboxId = mailboxId; 143 if (mResumed) { 144 highlightSelectedMailbox(true); 145 } 146 } 147 148 /** 149 * Called when the Fragment is visible to the user. 150 */ 151 @Override 152 public void onStart() { 153 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 154 Log.d(Email.LOG_TAG, "MailboxListFragment onStart"); 155 } 156 super.onStart(); 157 } 158 159 /** 160 * Called when the fragment is visible to the user and actively running. 161 */ 162 @Override 163 public void onResume() { 164 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 165 Log.d(Email.LOG_TAG, "MailboxListFragment onResume"); 166 } 167 super.onResume(); 168 mResumed = true; 169 170 // If we're recovering from the stopped state, we don't have to reload. 171 // (when mOpenRequested = false) 172 if (mAccountId != -1 && mOpenRequested) { 173 startLoading(); 174 } 175 } 176 177 @Override 178 public void onPause() { 179 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 180 Log.d(Email.LOG_TAG, "MailboxListFragment onPause"); 181 } 182 mResumed = false; 183 super.onPause(); 184 mSavedListState = new Utility.ListStateSaver(getListView()); 185 } 186 187 /** 188 * Called when the Fragment is no longer started. 189 */ 190 @Override 191 public void onStop() { 192 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 193 Log.d(Email.LOG_TAG, "MailboxListFragment onStop"); 194 } 195 super.onStop(); 196 } 197 198 /** 199 * Called when the fragment is no longer in use. 200 */ 201 @Override 202 public void onDestroy() { 203 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 204 Log.d(Email.LOG_TAG, "MailboxListFragment onDestroy"); 205 } 206 super.onDestroy(); 207 } 208 209 @Override 210 public void onSaveInstanceState(Bundle outState) { 211 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 212 Log.d(Email.LOG_TAG, "MailboxListFragment onSaveInstanceState"); 213 } 214 super.onSaveInstanceState(outState); 215 outState.putLong(BUNDLE_KEY_SELECTED_MAILBOX_ID, mSelectedMailboxId); 216 outState.putParcelable(BUNDLE_LIST_STATE, new Utility.ListStateSaver(getListView())); 217 } 218 219 private void restoreInstanceState(Bundle savedInstanceState) { 220 mSelectedMailboxId = savedInstanceState.getLong(BUNDLE_KEY_SELECTED_MAILBOX_ID); 221 mSavedListState = savedInstanceState.getParcelable(BUNDLE_LIST_STATE); 222 } 223 224 private void startLoading() { 225 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 226 Log.d(Email.LOG_TAG, "MailboxListFragment startLoading"); 227 } 228 mOpenRequested = false; 229 // Clear the list. (ListFragment will show the "Loading" animation) 230 setListShown(false); 231 232 // If we've already loaded for a different account, discard the previous result and 233 // start loading again. 234 // We don't want to use restartLoader(), because if the Loader is retained, we *do* want to 235 // reuse the previous result. 236 // Also, when changing accounts, we don't preserve scroll position. 237 boolean accountChanging = false; 238 if ((mLastLoadedAccountId != -1) && (mLastLoadedAccountId != mAccountId)) { 239 accountChanging = true; 240 getLoaderManager().stopLoader(LOADER_ID_MAILBOX_LIST); 241 242 // Also, when we're changing account, update the mailbox list if stale. 243 refreshMailboxListIfStale(); 244 } 245 getLoaderManager().initLoader(LOADER_ID_MAILBOX_LIST, null, 246 new MailboxListLoaderCallbacks(accountChanging)); 247 } 248 249 private class MailboxListLoaderCallbacks implements LoaderCallbacks<Cursor> { 250 private boolean mAccountChanging; 251 252 public MailboxListLoaderCallbacks(boolean accountChanging) { 253 mAccountChanging = accountChanging; 254 } 255 256 @Override 257 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 258 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 259 Log.d(Email.LOG_TAG, "MailboxListFragment onCreateLoader"); 260 } 261 return MailboxesAdapter.createLoader(getActivity(), mAccountId, 262 MailboxesAdapter.MODE_NORMAL); 263 } 264 265 @Override 266 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { 267 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 268 Log.d(Email.LOG_TAG, "MailboxListFragment onLoadFinished"); 269 } 270 mLastLoadedAccountId = mAccountId; 271 272 // Save list view state (primarily scroll position) 273 final ListView lv = getListView(); 274 final Utility.ListStateSaver lss; 275 if (mAccountChanging) { 276 lss = null; // Don't preserve list state 277 } else if (mSavedListState != null) { 278 lss = mSavedListState; 279 mSavedListState = null; 280 } else { 281 lss = new Utility.ListStateSaver(lv); 282 } 283 284 if (cursor.getCount() == 0) { 285 // If there's no row, don't set it to the ListView. 286 // Instead use setListShown(false) to make ListFragment show progress icon. 287 mListAdapter.changeCursor(null); 288 setListShown(false); 289 } else { 290 // Set the adapter. 291 mListAdapter.changeCursor(cursor); 292 setListAdapter(mListAdapter); 293 setListShown(true); 294 295 // We want to make selection visible only when account is changing.. 296 // i.e. Refresh caused by content changed events shouldn't scroll the list. 297 highlightSelectedMailbox(mAccountChanging); 298 } 299 300 // Restore the state 301 if (lss != null) { 302 lss.restore(lv); 303 } 304 305 // Clear this for next reload triggered by content changed events. 306 mAccountChanging = false; 307 } 308 } 309 310 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 311 // Don't use the id parameter. See MailboxesAdapter. 312 mCallback.onMailboxSelected(mAccountId, mListAdapter.getMailboxId(position)); 313 } 314 315 public void onRefresh() { 316 if (mAccountId != -1) { 317 mRefreshManager.refreshMailboxList(mAccountId); 318 } 319 } 320 321 private void refreshMailboxListIfStale() { 322 if (mRefreshManager.isMailboxListStale(mAccountId)) { 323 mRefreshManager.refreshMailboxList(mAccountId); 324 } 325 } 326 327 /** 328 * Highlight the selected mailbox. 329 */ 330 private void highlightSelectedMailbox(boolean ensureSelectionVisible) { 331 if (mSelectedMailboxId == -1) { 332 // No mailbox selected 333 mListView.clearChoices(); 334 return; 335 } 336 final int count = mListView.getCount(); 337 for (int i = 0; i < count; i++) { 338 if (mListAdapter.getMailboxId(i) != mSelectedMailboxId) { 339 continue; 340 } 341 mListView.setItemChecked(i, true); 342 if (ensureSelectionVisible) { 343 Log.w(Email.LOG_TAG, "MailboxListFragment -- ensure visible"); 344 Utility.listViewSmoothScrollToPosition(getActivity(), mListView, i); 345 } 346 break; 347 } 348 } 349} 350