ActionBarController.java revision f69bcd0123dff62ec916376470c6dd9a71e0d443
1/* 2 * Copyright (C) 2011 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 android.app.ActionBar; 20import android.app.LoaderManager; 21import android.app.LoaderManager.LoaderCallbacks; 22import android.content.Context; 23import android.content.Loader; 24import android.database.Cursor; 25import android.os.Bundle; 26import android.text.TextUtils; 27import android.util.Log; 28import android.view.LayoutInflater; 29import android.view.View; 30import android.widget.SearchView; 31import android.widget.TextView; 32 33import com.android.email.R; 34import com.android.emailcommon.Logging; 35import com.android.emailcommon.provider.Account; 36import com.android.emailcommon.provider.Mailbox; 37 38/** 39 * Manages the account name and the custom view part on the action bar. 40 * 41 * TODO Show current mailbox name/unread count on the account spinner 42 * -- and remove mMailboxNameContainer. 43 * 44 * TODO Stop using the action bar spinner and create our own spinner as a custom view. 45 * (so we'll be able to just hide it, etc.) 46 * 47 * TODO Update search hint somehow 48 */ 49public class ActionBarController { 50 private static final String BUNDLE_KEY_MODE = "ActionBarController.BUNDLE_KEY_MODE"; 51 52 /** 53 * Constants for {@link #mSearchMode}. 54 * 55 * In {@link #MODE_NORMAL} mode, we don't show the search box. 56 * In {@link #MODE_SEARCH} mode, we do show the search box. 57 * The action bar doesn't really care if the activity is showing search results. 58 * If the activity is showing search results, and the {@link Callback#onSearchExit} is called, 59 * the activity probably wants to close itself, but this class doesn't make the desision. 60 */ 61 private static final int MODE_NORMAL = 0; 62 private static final int MODE_SEARCH = 1; 63 64 private static final int LOADER_ID_ACCOUNT_LIST 65 = EmailActivity.ACTION_BAR_CONTROLLER_LOADER_ID_BASE + 0; 66 67 private final Context mContext; 68 private final LoaderManager mLoaderManager; 69 private final ActionBar mActionBar; 70 71 private final View mActionBarCustomView; 72 private final View mMailboxNameContainer; 73 private final TextView mMailboxNameView; 74 private final TextView mUnreadCountView; 75 private final View mSearchContainer; 76 private final SearchView mSearchView; 77 78 private final ActionBarNavigationCallback mActionBarNavigationCallback = 79 new ActionBarNavigationCallback(); 80 81 private final AccountSelectorAdapter mAccountsSelectorAdapter; 82 private AccountSelectorAdapter.CursorWithExtras mAccountCursor; 83 /** The current account ID; used to determine if the account has changed. */ 84 private long mLastAccountIdForDirtyCheck = Account.NO_ACCOUNT; 85 86 /** Either {@link #MODE_NORMAL} or {@link #MODE_SEARCH}. */ 87 private int mSearchMode = MODE_NORMAL; 88 89 public final Callback mCallback; 90 91 public interface SearchContext { 92 public long getTargetMailboxId(); 93 } 94 95 public interface Callback { 96 /** @return true if an account is selected. */ 97 public boolean isAccountSelected(); 98 99 /** 100 * @return currently selected account ID, {@link Account#ACCOUNT_ID_COMBINED_VIEW}, 101 * or -1 if no account is selected. 102 */ 103 public long getUIAccountId(); 104 105 /** @return true if the current mailbox name should be shown. */ 106 public boolean shouldShowMailboxName(); 107 108 /** @return current mailbox name */ 109 public String getCurrentMailboxName(); 110 /** 111 * @return unread count for the current mailbox. (0 if the mailbox doesn't have the concept 112 * of "unread"; e.g. Drafts) 113 */ 114 public int getCurrentMailboxUnreadCount(); 115 116 /** @return the "UP" arrow should be shown. */ 117 public boolean shouldShowUp(); 118 119 /** 120 * Called when an account is selected on the account spinner. 121 * @param accountId ID of the selected account, or {@link Account#ACCOUNT_ID_COMBINED_VIEW}. 122 */ 123 public void onAccountSelected(long accountId); 124 125 /** 126 * Invoked when a recent mailbox is selected on the account spinner. 127 * @param mailboxId The ID of the selected mailbox, or {@link Mailbox#NO_MAILBOX} if the 128 * special option "show all mailboxes" was selected. 129 */ 130 public void onMailboxSelected(long mailboxId); 131 132 /** Called when no accounts are found in the database. */ 133 public void onNoAccountsFound(); 134 135 /** 136 * Called when a search is submitted. 137 * 138 * @param queryTerm query string 139 */ 140 public void onSearchSubmit(String queryTerm); 141 142 /** 143 * Called when the search box is closed. 144 */ 145 public void onSearchExit(); 146 } 147 148 public ActionBarController(Context context, LoaderManager loaderManager, 149 ActionBar actionBar, Callback callback) { 150 mContext = context; 151 mLoaderManager = loaderManager; 152 mActionBar = actionBar; 153 mCallback = callback; 154 mAccountsSelectorAdapter = new AccountSelectorAdapter(mContext); 155 156 mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE 157 | ActionBar.DISPLAY_SHOW_HOME 158 | ActionBar.DISPLAY_SHOW_CUSTOM); 159 160 // Prepare the custom view 161 final LayoutInflater inflater = LayoutInflater.from(mContext); 162 mActionBarCustomView = inflater.inflate(R.layout.action_bar_custom_view, null); 163 final ActionBar.LayoutParams customViewLayout = new ActionBar.LayoutParams( 164 ActionBar.LayoutParams.WRAP_CONTENT, 165 ActionBar.LayoutParams.MATCH_PARENT); 166 customViewLayout.setMargins(0 , 0, 0, 0); 167 mActionBar.setCustomView(mActionBarCustomView, customViewLayout); 168 169 // Mailbox name / unread count 170 mMailboxNameContainer = UiUtilities.getView(mActionBarCustomView, 171 R.id.current_mailbox_container); 172 mMailboxNameView = UiUtilities.getView(mMailboxNameContainer, R.id.mailbox_name); 173 mUnreadCountView = UiUtilities.getView(mMailboxNameContainer, R.id.unread_count); 174 175 // Search 176 mSearchContainer = UiUtilities.getView(mActionBarCustomView, R.id.search_container); 177 mSearchView = UiUtilities.getView(mSearchContainer, R.id.search_view); 178 mSearchView.setSubmitButtonEnabled(true); 179 mSearchView.setOnQueryTextListener(mOnQueryText); 180 } 181 182 /** Must be called from {@link UIControllerBase#onActivityCreated()} */ 183 public void onActivityCreated() { 184 loadAccounts(); 185 refresh(); 186 } 187 188 /** Must be called from {@link UIControllerBase#onActivityDestroy()} */ 189 public void onActivityDestroy() { 190 } 191 192 /** Must be called from {@link UIControllerBase#onSaveInstanceState} */ 193 public void onSaveInstanceState(Bundle outState) { 194 outState.putInt(BUNDLE_KEY_MODE, mSearchMode); 195 } 196 197 /** Must be called from {@link UIControllerBase#onRestoreInstanceState} */ 198 public void onRestoreInstanceState(Bundle savedState) { 199 int mode = savedState.getInt(BUNDLE_KEY_MODE); 200 if (mode == MODE_SEARCH) { 201 // No need to re-set the initial query, as the View tree restoration does that 202 enterSearchMode(null); 203 } 204 } 205 206 /** 207 * @return true if the search box is shown. 208 */ 209 private boolean isInSearchMode() { 210 return mSearchMode == MODE_SEARCH; 211 } 212 213 /** 214 * Show the search box. 215 * 216 * @param initialQueryTerm if non-empty, set to the search box. 217 */ 218 public void enterSearchMode(String initialQueryTerm) { 219 if (isInSearchMode()) { 220 return; 221 } 222 if (!TextUtils.isEmpty(initialQueryTerm)) { 223 mSearchView.setQuery(initialQueryTerm, false); 224 } 225 mSearchMode = MODE_SEARCH; 226 227 // Need to force it to mode "standard" to hide it. 228 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); 229 mActionBar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); 230 mSearchContainer.setVisibility(View.VISIBLE); 231 // TODO: HACK. this is a workaround IME not popping up. 232 mSearchView.setIconified(false); 233 234 refresh(); 235 } 236 237 private void exitSearchMode() { 238 if (!isInSearchMode()) { 239 return; 240 } 241 mSearchMode = MODE_NORMAL; 242 mSearchContainer.setVisibility(View.GONE); 243 mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE); 244 245 // Force update of account list when we exit search. 246 updateAccountList(); 247 248 refresh(); 249 mCallback.onSearchExit(); 250 } 251 252 /** 253 * Performs the back action. 254 * 255 * @param isSystemBackKey <code>true</code> if the system back key was pressed. 256 * <code>false</code> if it's caused by the "home" icon click on the action bar. 257 */ 258 public boolean onBackPressed(boolean isSystemBackKey) { 259 if (isInSearchMode()) { 260 exitSearchMode(); 261 return true; 262 } 263 return false; 264 } 265 266 /** Refreshes the action bar display. */ 267 public void refresh() { 268 final boolean showUp = isInSearchMode() || mCallback.shouldShowUp(); 269 mActionBar.setDisplayOptions(showUp 270 ? ActionBar.DISPLAY_HOME_AS_UP : 0, ActionBar.DISPLAY_HOME_AS_UP); 271 272 if (isInSearchMode()) { 273 mMailboxNameContainer.setVisibility(View.GONE); 274 } else { 275 mMailboxNameContainer.setVisibility(mCallback.shouldShowMailboxName() 276 ? View.VISIBLE : View.GONE); 277 mMailboxNameView.setText(mCallback.getCurrentMailboxName()); 278 } 279 280 // Note on action bar, we show only "unread count". Some mailboxes such as Outbox don't 281 // have the idea of "unread count", in which case we just omit the count. 282 mUnreadCountView.setText(UiUtilities.getMessageCountForUi(mContext, 283 mCallback.getCurrentMailboxUnreadCount(), true)); 284 285 // Update the account list only when the account has changed. 286 if (mLastAccountIdForDirtyCheck != mCallback.getUIAccountId()) { 287 mLastAccountIdForDirtyCheck = mCallback.getUIAccountId(); 288 // If the selected account changes, reload the cursor to update the recent mailboxes 289 if (mLastAccountIdForDirtyCheck != Account.NO_ACCOUNT) { 290 mLoaderManager.destroyLoader(LOADER_ID_ACCOUNT_LIST); 291 loadAccounts(); 292 } else { 293 updateAccountList(); 294 } 295 } 296 } 297 298 /** 299 * Load account cursor, and update the action bar. 300 */ 301 private void loadAccounts() { 302 mLoaderManager.initLoader(LOADER_ID_ACCOUNT_LIST, null, 303 new LoaderCallbacks<Cursor>() { 304 @Override 305 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 306 return AccountSelectorAdapter.createLoader(mContext, mCallback.getUIAccountId()); 307 } 308 309 @Override 310 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 311 mAccountCursor = (AccountSelectorAdapter.CursorWithExtras) data; 312 updateAccountList(); 313 } 314 315 @Override 316 public void onLoaderReset(Loader<Cursor> loader) { 317 mAccountCursor = null; 318 updateAccountList(); 319 } 320 }); 321 } 322 323 /** 324 * Called when the LOADER_ID_ACCOUNT_LIST loader loads the data. Update the account spinner 325 * on the action bar. 326 */ 327 private void updateAccountList() { 328 mAccountsSelectorAdapter.swapCursor(mAccountCursor); 329 330 if (mSearchMode == MODE_SEARCH) { 331 // In search mode, so we don't care about the account list - it'll get updated when 332 // it goes visible again. 333 return; 334 } 335 336 final ActionBar ab = mActionBar; 337 if (mAccountCursor == null) { 338 // Cursor not ready or closed. 339 ab.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); 340 ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); 341 return; 342 } 343 344 final int count = mAccountCursor.mAccountCount + mAccountCursor.mRecentCount; 345 if (count == 0) { 346 mCallback.onNoAccountsFound(); 347 return; 348 } 349 350 // If only one account, don't show the drop down. 351 int selectedPosition = mAccountCursor.getPosition(mCallback.getUIAccountId()); 352 if (count == 1) { 353 // Show the account name as the title. 354 ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE); 355 ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); 356 if (selectedPosition >= 0) { 357 mAccountCursor.moveToPosition(selectedPosition); 358 ab.setTitle(AccountSelectorAdapter.getAccountDisplayName(mAccountCursor)); 359 } 360 return; 361 } 362 363 // Update the drop down list. 364 if (ab.getNavigationMode() != ActionBar.NAVIGATION_MODE_LIST) { 365 ab.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); 366 ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); 367 ab.setListNavigationCallbacks(mAccountsSelectorAdapter, mActionBarNavigationCallback); 368 } 369 // Find the currently selected account, and select it. 370 if (selectedPosition >= 0) { 371 ab.setSelectedNavigationItem(selectedPosition); 372 } 373 } 374 375 private class ActionBarNavigationCallback implements ActionBar.OnNavigationListener { 376 @Override 377 public boolean onNavigationItemSelected(int itemPosition, long itemId) { 378 if (mAccountsSelectorAdapter.isAccountItem(itemPosition) 379 && itemId != mCallback.getUIAccountId()) { 380 mCallback.onAccountSelected(itemId); 381 } else if (mAccountsSelectorAdapter.isMailboxItem(itemPosition)) { 382 mCallback.onMailboxSelected(itemId); 383 // We need to update the selection, otherwise the user is unable to select the 384 // recent folder a second time w/o first selecting another item in the spinner 385 int selectedPosition = mAccountsSelectorAdapter.getAccountPosition(itemPosition); 386 if (selectedPosition != AccountSelectorAdapter.UNKNOWN_POSITION) { 387 mActionBar.setSelectedNavigationItem(selectedPosition); 388 } 389 } else { 390 Log.i(Logging.LOG_TAG, 391 "Invalid type selected in ActionBarController at index " + itemPosition); 392 } 393 return true; 394 } 395 } 396 397 private final SearchView.OnQueryTextListener mOnQueryText 398 = new SearchView.OnQueryTextListener() { 399 @Override 400 public boolean onQueryTextChange(String newText) { 401 // Event not handled. Let the search do the default action. 402 return false; 403 } 404 405 @Override 406 public boolean onQueryTextSubmit(String query) { 407 mCallback.onSearchSubmit(mSearchView.getQuery().toString()); 408 return true; // Event handled. 409 } 410 }; 411 412} 413