blob: de33db5dc3aec0c9d42a43c89986e335bc9a6d1c [file] [log] [blame]
Mindy Pereira68f2e222012-03-07 10:36:54 -08001/*
2 * Copyright (C) 2012 Google Inc.
3 * Licensed to The Android Open Source Project.
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -08004 *
Mindy Pereira68f2e222012-03-07 10:36:54 -08005 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -08008 *
Mindy Pereira68f2e222012-03-07 10:36:54 -08009 * http://www.apache.org/licenses/LICENSE-2.0
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -080010 *
Mindy Pereira68f2e222012-03-07 10:36:54 -080011 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -080017
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080018package com.android.mail.ui;
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -080019
20import android.app.ActionBar;
Mindy Pereira68f2e222012-03-07 10:36:54 -080021import android.app.SearchManager;
22import android.app.SearchableInfo;
Vikram Aggarwal2175d0a2012-02-17 16:04:23 -080023import android.app.ActionBar.OnNavigationListener;
Mindy Pereira68f2e222012-03-07 10:36:54 -080024import android.content.Context;
Vikram Aggarwal12a7c042012-03-28 16:48:08 -070025import android.database.Cursor;
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -080026import android.os.Bundle;
Mindy Pereira9b623802012-03-07 17:15:49 -080027import android.os.Handler;
Mindy Pereira68f2e222012-03-07 10:36:54 -080028import android.util.AttributeSet;
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -080029import android.view.Menu;
Mindy Pereira68f2e222012-03-07 10:36:54 -080030import android.view.MenuItem;
31import android.view.View;
32import android.widget.LinearLayout;
33import android.widget.SearchView;
34import android.widget.SearchView.OnQueryTextListener;
Vikram Aggarwal12a7c042012-03-28 16:48:08 -070035import android.widget.SearchView.OnSuggestionListener;
Mindy Pereira68f2e222012-03-07 10:36:54 -080036import android.widget.TextView;
37import android.widget.Toast;
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -080038
Mindy Pereira68f2e222012-03-07 10:36:54 -080039import com.android.mail.R;
40import com.android.mail.AccountSpinnerAdapter;
41import com.android.mail.ConversationListContext;
Vikram Aggarwala9b93f32012-02-23 14:51:58 -080042import com.android.mail.providers.Account;
Mindy Pereira68f2e222012-03-07 10:36:54 -080043import com.android.mail.providers.UIProvider.AccountCapabilities;
Mindy Pereira9b623802012-03-07 17:15:49 -080044import com.android.mail.providers.UIProvider.LastSyncResult;
Mindy Pereiraf9323cd2012-02-29 13:47:09 -080045import com.android.mail.providers.Folder;
Andy Huang0d647352012-03-21 21:48:16 -070046import com.android.mail.utils.LogUtils;
Mindy Pereira9b623802012-03-07 17:15:49 -080047import com.android.mail.utils.Utils;
Vikram Aggarwala9b93f32012-02-23 14:51:58 -080048
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -080049/**
Mindy Pereira68f2e222012-03-07 10:36:54 -080050 * View to manage the various states of the Mail Action Bar
51 *
52 * TODO(viki): Include ConversationSubjectDisplayer here as well.
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -080053 */
Mindy Pereira68f2e222012-03-07 10:36:54 -080054public final class ActionBarView extends LinearLayout implements OnNavigationListener,
Vikram Aggarwal12a7c042012-03-28 16:48:08 -070055 ViewMode.ModeChangeListener, OnQueryTextListener, OnSuggestionListener {
Mindy Pereira68f2e222012-03-07 10:36:54 -080056 private ActionBar mActionBar;
57 private RestrictedActivity mActivity;
Vikram Aggarwala7997952012-03-09 10:30:13 -080058 private ActivityController mController;
Mindy Pereira68f2e222012-03-07 10:36:54 -080059 private View mFolderView;
60 /**
61 * The current mode of the ActionBar. This references constants in {@link ViewMode}
62 */
63 private int mMode = ViewMode.UNKNOWN;
64
65 private MenuItem mSearch;
66 AccountSpinnerAdapter mSpinner;
67 /**
68 * The account currently being shown
69 */
70 private Account mAccount;
71
72 // TODO(viki): This is a SnippetTextView in the Gmail source code. Resolve.
73 private TextView mSubjectView;
74 private SearchView mSearchWidget;
75 private MenuItem mHelpItem;
Paul Westbrook517743e2012-03-22 10:40:46 -070076 private MenuItem mSendFeedbackItem;
Mindy Pereira9b623802012-03-07 17:15:49 -080077 private MenuItem mRefreshItem;
78 private View mRefreshActionView;
79 private boolean mRefreshInProgress;
80
Andy Huang0d647352012-03-21 21:48:16 -070081 public static final String LOG_TAG = new LogUtils().getLogTag();
82
Mindy Pereira9b623802012-03-07 17:15:49 -080083 private final Handler mHandler = new Handler();
84 private final Runnable mInvalidateMenu = new Runnable() {
85 @Override
86 public void run() {
87 mActivity.invalidateOptionsMenu();
88 }
89 };
Mindy Pereira68f2e222012-03-07 10:36:54 -080090
91 public ActionBarView(Context context) {
92 this(context, null);
93 }
94
95 public ActionBarView(Context context, AttributeSet attrs) {
96 this(context, attrs, 0);
97 }
98
99 public ActionBarView(Context context, AttributeSet attrs, int defStyle) {
100 super(context, attrs, defStyle);
101 }
102
103 public boolean onCreateOptionsMenu(Menu menu) {
104 // If the mode is valid, then set the initial menu
105 if (mMode == ViewMode.UNKNOWN) {
106 return false;
107 }
Mindy Pereira9b623802012-03-07 17:15:49 -0800108 mSearch = menu.findItem(R.id.search);
109 if (mSearch != null) {
110 mSearchWidget = (SearchView) mSearch.getActionView();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800111 SearchManager searchManager = (SearchManager) mActivity.getActivityContext()
112 .getSystemService(Context.SEARCH_SERVICE);
Mindy Pereirafb178492012-03-07 18:56:42 -0800113 if (searchManager != null && mSearchWidget != null) {
Mindy Pereira68f2e222012-03-07 10:36:54 -0800114 SearchableInfo info = searchManager.getSearchableInfo(mActivity.getComponentName());
115 mSearchWidget.setSearchableInfo(info);
116 mSearchWidget.setOnQueryTextListener(this);
Vikram Aggarwal12a7c042012-03-28 16:48:08 -0700117 mSearchWidget.setOnSuggestionListener(this);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800118 }
119 }
120 mHelpItem = menu.findItem(R.id.help_info_menu_item);
Paul Westbrook517743e2012-03-22 10:40:46 -0700121 mSendFeedbackItem = menu.findItem(R.id.feedback_menu_item);
Mindy Pereira9b623802012-03-07 17:15:49 -0800122 mRefreshItem = menu.findItem(R.id.refresh);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800123 return true;
124 }
125
126 public int getOptionsMenuId() {
127 // Relies on the ordering of the view modes, since they are integer constants.
128 final int[] modeMenu = {
129 // 0: UNKNOWN
130 R.menu.conversation_list_menu,
131 // 1: CONVERSATION
132 R.menu.conversation_actions,
133 // 2: CONVERSATION_LIST
134 R.menu.conversation_list_menu,
135 // 3: FOLDER_LIST
136 R.menu.folder_list_menu,
137 // 4: SEARCH_RESULTS_LIST
138 R.menu.conversation_list_search_results_actions,
139 // 5: SEARCH_RESULTS_CONVERSATION
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800140 R.menu.conversation_search_results_actions
Mindy Pereira68f2e222012-03-07 10:36:54 -0800141 };
142 return modeMenu[mMode];
143 }
144
145 public void handleRestore(Bundle savedInstanceState) {
146 }
147
148 public void handleSaveInstanceState(Bundle outState) {
149 }
150
151 public void initialize(RestrictedActivity activity, ActivityController callback,
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800152 ViewMode viewMode, ActionBar actionBar, RecentFolderList recentFolders) {
Mindy Pereira68f2e222012-03-07 10:36:54 -0800153 mActionBar = actionBar;
Vikram Aggarwala7997952012-03-09 10:30:13 -0800154 mController = callback;
Mindy Pereira68f2e222012-03-07 10:36:54 -0800155 mActivity = activity;
156
Vikram Aggarwalec5cbf72012-03-08 15:10:35 -0800157 mSpinner = new AccountSpinnerAdapter(getContext(), recentFolders);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800158 // Set the mode to Navigation mode and listen on navigation changes.
159 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
160 mActionBar.setListNavigationCallbacks(mSpinner, this);
161 }
162
163 public void setAccounts(Account[] accounts) {
Vikram Aggarwala7997952012-03-09 10:30:13 -0800164 Account currentAccount = mController.getCurrentAccount();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800165 mSpinner.setAccounts(accounts);
Andy Huang0d647352012-03-21 21:48:16 -0700166
167 int position;
168 for (position = 0; position < accounts.length; position++) {
169 if (accounts[position].equals(currentAccount)) {
170 break;
Mindy Pereira68f2e222012-03-07 10:36:54 -0800171 }
172 }
Andy Huang0d647352012-03-21 21:48:16 -0700173 if (position >= accounts.length) {
174 position = 0;
175 LogUtils.w(LOG_TAG, "IN actionbarview setAccounts, account not found, using first.");
176 }
Vikram Aggarwal3a104732012-03-27 11:35:17 -0700177
178 setSelectedPosition(position);
179 }
180
181 /**
182 * Sets the selected navigation position in the spinner to the position given here.
183 * @param position
184 */
185 private void setSelectedPosition(int position) {
186 // Only change the position if we are in the correct mode.
187 if (mActionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_LIST)
188 return;
Andy Huang0d647352012-03-21 21:48:16 -0700189 mActionBar.setSelectedNavigationItem(position);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800190 }
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -0800191
192 /**
Mindy Pereira68f2e222012-03-07 10:36:54 -0800193 * Called by the owner of the ActionBar to set the
194 * folder that is currently being displayed.
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -0800195 */
Mindy Pereira68f2e222012-03-07 10:36:54 -0800196 public void setFolder(Folder folder) {
Vikram Aggarwale6340bc2012-03-26 15:57:09 -0700197 // Change the currently selected item to an element which is a spacer: valid but not useful
198 // This allows us to receive a tap on the account name when the user taps on it, and we can
199 // take the user to the default inbox.
Vikram Aggarwal3a104732012-03-27 11:35:17 -0700200 setSelectedPosition(mSpinner.getSpacerPosition());
Mindy Pereira68f2e222012-03-07 10:36:54 -0800201 mSpinner.setCurrentFolder(folder);
202 mSpinner.notifyDataSetChanged();
203 }
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -0800204
205 /**
Mindy Pereira68f2e222012-03-07 10:36:54 -0800206 * Called by the owner of the ActionBar to set the
207 * account that is currently being displayed.
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -0800208 */
Mindy Pereira68f2e222012-03-07 10:36:54 -0800209 public void setAccount(Account account) {
Mindy Pereira9b623802012-03-07 17:15:49 -0800210 mAccount = account;
Mindy Pereira68f2e222012-03-07 10:36:54 -0800211 mSpinner.setCurrentAccount(account);
212 mSpinner.notifyDataSetChanged();
213 }
214
215 @Override
216 public boolean onNavigationItemSelected(int position, long id) {
Marc Blank41e764d2012-03-20 20:46:28 -0700217 final int type = mSpinner.getType(position);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800218 switch (type) {
219 case AccountSpinnerAdapter.TYPE_ACCOUNT:
220 // Get the capabilities associated with this account.
Marc Blank41e764d2012-03-20 20:46:28 -0700221 final Object account = mSpinner.getItem(position);
222 assert (account instanceof Account);
223 mController.onAccountChanged((Account) account);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800224 break;
225 case AccountSpinnerAdapter.TYPE_FOLDER:
226 final Object folder = mSpinner.getItem(position);
227 assert (folder instanceof Folder);
Vikram Aggarwala7997952012-03-09 10:30:13 -0800228 mController.onFolderChanged((Folder) folder);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800229 break;
230 }
231 return false;
232 }
233
234 public void onPause() {
235 }
236
237 public void onResume() {
238 }
239
Vikram Aggarwala7997952012-03-09 10:30:13 -0800240 @Override
Mindy Pereira68f2e222012-03-07 10:36:54 -0800241 public void onViewModeChanged(int newMode) {
242 mMode = newMode;
243 // Always update the options menu and redraw. This will read the new mode and redraw
244 // the options menu.
245 mActivity.invalidateOptionsMenu();
246 }
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -0800247
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800248 public boolean onPrepareOptionsMenu(Menu menu) {
Mindy Pereira68f2e222012-03-07 10:36:54 -0800249 // We start out with every option enabled. Based on the current view, we disable actions
250 // that are possible.
251 if (mSubjectView != null){
252 mSubjectView.setVisibility(GONE);
253 }
254 if (mFolderView != null){
255 mFolderView.setVisibility(GONE);
256 }
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -0800257
Mindy Pereira9b623802012-03-07 17:15:49 -0800258 if (mRefreshInProgress) {
259 if (mRefreshItem != null) {
260 if (mRefreshActionView == null) {
261 mRefreshItem.setActionView(R.layout.action_bar_indeterminate_progress);
262 mRefreshActionView = mRefreshItem.getActionView();
263 } else {
264 mRefreshItem.setActionView(mRefreshActionView);
265 }
266 }
267 } else {
268 if (mRefreshItem != null) {
269 mRefreshItem.setActionView(null);
270 }
271 }
Mindy Pereira68f2e222012-03-07 10:36:54 -0800272 if (mHelpItem != null) {
273 mHelpItem.setVisible(mAccount != null
274 && mAccount.supportsCapability(AccountCapabilities.HELP_CONTENT));
275 }
Paul Westbrook517743e2012-03-22 10:40:46 -0700276 if (mSendFeedbackItem != null) {
277 mSendFeedbackItem.setVisible(mAccount != null
278 && mAccount.supportsCapability(AccountCapabilities.SEND_FEEDBACK));
279 }
Mindy Pereira9b623802012-03-07 17:15:49 -0800280 switch (mMode) {
Mindy Pereira68f2e222012-03-07 10:36:54 -0800281 case ViewMode.UNKNOWN:
Mindy Pereira9b623802012-03-07 17:15:49 -0800282 if (mSearch != null) {
Mindy Pereira68f2e222012-03-07 10:36:54 -0800283 mSearch.collapseActionView();
284 }
285 break;
286 case ViewMode.CONVERSATION_LIST:
Mindy Pereira30fd47b2012-03-09 09:24:00 -0800287 // Show compose, search, folders, and sync based on the account
Mindy Pereira68f2e222012-03-07 10:36:54 -0800288 // The only option that needs to be disabled is search
Mindy Pereira72a9f392012-03-21 09:41:09 -0700289 showNavList();
Mindy Pereira863e4412012-03-23 12:21:38 -0700290 Utils.setMenuItemVisibility(menu, R.id.search,
Mindy Pereira9b623802012-03-07 17:15:49 -0800291 mAccount.supportsCapability(AccountCapabilities.FOLDER_SERVER_SEARCH));
Mindy Pereira68f2e222012-03-07 10:36:54 -0800292 break;
293 case ViewMode.CONVERSATION:
Mindy Pereira72a9f392012-03-21 09:41:09 -0700294 mActionBar.setDisplayHomeAsUpEnabled(true);
295 showNavList();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800296 break;
297 case ViewMode.SEARCH_RESULTS_LIST:
Mindy Pereira72a9f392012-03-21 09:41:09 -0700298 showNavList();
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800299 setPopulatedSearchView();
Mindy Pereira68f2e222012-03-07 10:36:54 -0800300 break;
301 case ViewMode.SEARCH_RESULTS_CONVERSATION:
302 mActionBar.setDisplayHomeAsUpEnabled(true);
Mindy Pereira72a9f392012-03-21 09:41:09 -0700303 showNavList();
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800304 if (Utils.useTabletUI(mActivity.getActivityContext())) {
305 setPopulatedSearchView();
306 }
Mindy Pereira68f2e222012-03-07 10:36:54 -0800307 break;
308 case ViewMode.FOLDER_LIST:
Mindy Pereira72a9f392012-03-21 09:41:09 -0700309 mActionBar.setDisplayHomeAsUpEnabled(true);
310 mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE,
311 ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
312 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
313 mActionBar.setTitle(R.string.folder_list_title);
Mindy Pereira68f2e222012-03-07 10:36:54 -0800314 break;
315 }
316 return false;
317 }
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -0800318
Mindy Pereira72a9f392012-03-21 09:41:09 -0700319 private void showNavList() {
320 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
321 mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
322 ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
323 }
324
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800325 private void setPopulatedSearchView() {
326 if (mSearch != null) {
327 mSearch.expandActionView();
Vikram Aggarwala7997952012-03-09 10:30:13 -0800328 ConversationListContext context = mController.getCurrentListContext();
Mindy Pereirab849dfb2012-03-07 18:13:15 -0800329 if (context != null) {
330 mSearchWidget.setQuery(context.searchQuery, false);
331 }
332 }
333 }
334
Mindy Pereira68f2e222012-03-07 10:36:54 -0800335 public void removeBackButton() {
336 if (mActionBar == null) {
337 return;
338 }
339 mActionBar.setDisplayOptions(
340 ActionBar.DISPLAY_SHOW_HOME,
341 ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME);
342 mActivity.getActionBar().setHomeButtonEnabled(false);
343 }
Mindy Pereirabc57bf12012-02-29 14:39:09 -0800344
Mindy Pereira68f2e222012-03-07 10:36:54 -0800345 public void setBackButton() {
346 if (mActionBar == null){
347 return;
348 }
349 mActionBar.setDisplayOptions(
350 ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME,
351 ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME);
352 mActivity.getActionBar().setHomeButtonEnabled(true);
353 }
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -0800354
Mindy Pereira68f2e222012-03-07 10:36:54 -0800355 @Override
356 public boolean onQueryTextSubmit(String query) {
Mindy Pereira4ce59942012-03-08 09:57:35 -0800357 if (mSearch != null) {
358 mSearch.collapseActionView();
359 mSearchWidget.setQuery("", false);
360 }
Mindy Pereira68f2e222012-03-07 10:36:54 -0800361 mActivity.onSearchRequested(query);
362 return true;
363 }
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -0800364
Mindy Pereira68f2e222012-03-07 10:36:54 -0800365 @Override
366 public boolean onQueryTextChange(String newText) {
367 // TODO Auto-generated method stub
368 return false;
369 }
Mindy Pereira9b623802012-03-07 17:15:49 -0800370
371 public boolean setRefreshInProgress(boolean inProgress) {
372 if (inProgress != mRefreshInProgress) {
373 mRefreshInProgress = inProgress;
374 if (mSearch == null || !mSearch.isActionViewExpanded()) {
375 mHandler.post(mInvalidateMenu);
376 }
377 return true;
378 }
379 return false;
380 }
381
382 public void onRefreshStarted() {
383 setRefreshInProgress(true);
384 }
385
386 public void onRefreshStopped(int status) {
387 if (setRefreshInProgress(false)) {
388 switch (status) {
389 case LastSyncResult.SUCCESS:
390 break;
391 default:
392 Context context = mActivity.getActivityContext();
393 Toast.makeText(context, Utils.getSyncStatusText(context, status),
394 Toast.LENGTH_LONG).show();
395 break;
396 }
397 }
398 }
Mindy Pereirafd0c2972012-03-27 13:50:39 -0700399
400 /**
401 * Get the query text the user entered in the search widget, or empty string
402 * if there is none.
403 */
404 public String getQuery() {
405 return mSearchWidget != null ? mSearchWidget.getQuery().toString() : "";
406 }
Vikram Aggarwal12a7c042012-03-28 16:48:08 -0700407
408 // Next two methods are called when search suggestions are clicked.
409 @Override
410 public boolean onSuggestionSelect(int position) {
411 return onSuggestionClick(position);
412 }
413
414 @Override
415 public boolean onSuggestionClick(int position) {
416 final Cursor c = mSearchWidget.getSuggestionsAdapter().getCursor();
417 final boolean haveValidQuery = (c != null) && c.moveToPosition(position);
418 if (!haveValidQuery) {
419 LogUtils.d(LOG_TAG, "onSuggestionClick: Couldn't get a search query");
420 // We haven't handled this query, but the default behavior will leave EXTRA_ACCOUNT
421 // un-populated, leading to a crash. So claim that we have handled the event.
422 return true;
423 }
424 final String query = c.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY));
425 mController.onSearchRequested(query);
426 return true;
427 }
Mindy Pereiraa46a57f2012-04-03 17:19:31 -0700428
429 /**
430 * Notify that the folder has changed.
431 */
432 public void onFolderUpdated(Folder folder) {
433 mSpinner.onFolderUpdated(folder);
434 }
Vikram Aggarwal5e5ac742011-12-19 08:14:16 -0800435}