TwoPaneController.java revision 907e0d8e10457f535b47b9849873f4ab9bec72ad
1/******************************************************************************* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * 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 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * 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 *******************************************************************************/ 17 18package com.android.mail.ui; 19 20import android.app.Fragment; 21import android.app.FragmentManager; 22import android.app.FragmentTransaction; 23import android.content.Intent; 24import android.net.Uri; 25import android.os.Bundle; 26import android.view.Gravity; 27import android.widget.FrameLayout; 28 29import com.android.mail.ConversationListContext; 30import com.android.mail.R; 31import com.android.mail.providers.Account; 32import com.android.mail.providers.Conversation; 33import com.android.mail.providers.Folder; 34import com.android.mail.utils.LogUtils; 35import com.android.mail.utils.Utils; 36 37/** 38 * Controller for two-pane Mail activity. Two Pane is used for tablets, where screen real estate 39 * abounds. 40 */ 41 42// Called TwoPaneActivityController in Gmail. 43public final class TwoPaneController extends AbstractActivityController { 44 private TwoPaneLayout mLayout; 45 46 /** 47 * @param activity 48 * @param viewMode 49 */ 50 public TwoPaneController(MailActivity activity, ViewMode viewMode) { 51 super(activity, viewMode); 52 } 53 54 /** 55 * Display the conversation list fragment. 56 * @param show 57 */ 58 private void initializeConversationListFragment(boolean show) { 59 if (show) { 60 if (Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())) { 61 if (Utils.showTwoPaneSearchResults(mActivity.getActivityContext())) { 62 mViewMode.enterSearchResultsConversationMode(); 63 } else { 64 mViewMode.enterSearchResultsListMode(); 65 } 66 } else { 67 mViewMode.enterConversationListMode(); 68 } 69 } 70 renderConversationList(); 71 } 72 73 /** 74 * Render the conversation list in the correct pane. 75 */ 76 private void renderConversationList() { 77 if (mActivity == null) { 78 return; 79 } 80 FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction(); 81 // Use cross fading animation. 82 fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 83 Fragment conversationListFragment = ConversationListFragment.newInstance(mConvListContext); 84 fragmentTransaction.replace(R.id.conversation_list_pane, conversationListFragment, 85 TAG_CONVERSATION_LIST); 86 fragmentTransaction.commitAllowingStateLoss(); 87 } 88 89 /** 90 * Render the folder list in the correct pane. 91 */ 92 private void renderFolderList() { 93 if (mActivity == null) { 94 return; 95 } 96 createFolderListFragment(null, mAccount.folderListUri); 97 } 98 99 private void createFolderListFragment(Folder parent, Uri uri) { 100 FolderListFragment folderListFragment = FolderListFragment.newInstance(parent, uri); 101 FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction(); 102 fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 103 fragmentTransaction.replace(R.id.content_pane, folderListFragment, TAG_FOLDER_LIST); 104 fragmentTransaction.commitAllowingStateLoss(); 105 // Since we are showing the folder list, we are at the start of the view 106 // stack. 107 resetActionBarIcon(); 108 } 109 110 @Override 111 protected boolean isConversationListVisible() { 112 return !mLayout.isConversationListCollapsed(); 113 } 114 115 @Override 116 public void showConversationList(ConversationListContext listContext) { 117 super.showConversationList(listContext); 118 initializeConversationListFragment(true); 119 } 120 121 @Override 122 public void showFolderList() { 123 // On two-pane layouts, showing the folder list takes you to the top level of the 124 // application, which is the same as pressing the Up button 125 onUpPressed(); 126 } 127 128 @Override 129 public boolean onCreate(Bundle savedState) { 130 mActivity.setContentView(R.layout.two_pane_activity); 131 mLayout = (TwoPaneLayout) mActivity.findViewById(R.id.two_pane_activity); 132 if (mLayout == null) { 133 // We need the layout for everything. Crash early if it is null. 134 LogUtils.wtf(LOG_TAG, "mLayout is null!"); 135 } 136 mLayout.initializeLayout(mActivity.getApplicationContext(), 137 Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction())); 138 139 // The tablet layout needs to refer to mode changes. 140 mViewMode.addListener(mLayout); 141 // The activity controller needs to listen to layout changes. 142 mLayout.setListener(this); 143 final boolean isParentInitialized = super.onCreate(savedState); 144 return isParentInitialized; 145 } 146 147 @Override 148 public void onWindowFocusChanged(boolean hasFocus) { 149 if (hasFocus && !mLayout.isConversationListCollapsed()) { 150 // The conversation list is visible. 151 Utils.setConversationCursorVisibility(mConversationListCursor, true); 152 } 153 } 154 155 @Override 156 public void onAccountChanged(Account account) { 157 super.onAccountChanged(account); 158 renderFolderList(); 159 } 160 161 @Override 162 public void onFolderChanged(Folder folder) { 163 super.onFolderChanged(folder); 164 exitCabMode(); 165 final FolderListFragment folderList = getFolderListFragment(); 166 if (folderList != null) { 167 folderList.selectInitialFolder(folder); 168 } 169 } 170 171 @Override 172 public void onFolderSelected(Folder folder) { 173 super.onFolderSelected(folder); 174 if (folder.hasChildren) { 175 // Replace this fragment with a new FolderListFragment 176 // showing this folder's children if we are not already looking 177 // at the child view for this folder. 178 createFolderListFragment(folder, folder.childFoldersListUri); 179 // Show the up affordance when digging into child folders. 180 mActionBarView.setBackButton(); 181 return; 182 } 183 final FolderListFragment folderList = getFolderListFragment(); 184 if (folderList != null) { 185 folderList.selectInitialFolder(folder); 186 } 187 } 188 189 private void goUpFolderHierarchy(Folder current) { 190 Folder parent = current.parent; 191 if (parent.parent != null) { 192 super.onFolderSelected(parent); 193 createFolderListFragment(parent.parent, parent.parent.childFoldersListUri); 194 // Show the up affordance when digging into child folders. 195 mActionBarView.setBackButton(); 196 } else { 197 onFolderSelected(parent); 198 } 199 final FolderListFragment folderList = getFolderListFragment(); 200 if (folderList != null) { 201 folderList.selectInitialFolder(parent); 202 } 203 } 204 205 @Override 206 public void onViewModeChanged(int newMode) { 207 super.onViewModeChanged(newMode); 208 if (newMode != ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION) { 209 // Clear the wait fragment 210 hideWaitForInitialization(); 211 } 212 // In conversation mode, if the conversation list is not visible, then the user cannot 213 // see the selected conversations. Disable the CAB mode while leaving the selected set 214 // untouched. 215 // Otherwise, the conversation list is guaranteed to be visible. Try to enable the CAB 216 // mode if any conversations are selected. 217 if (newMode == ViewMode.CONVERSATION){ 218 enableOrDisableCab(); 219 } 220 resetActionBarIcon(); 221 } 222 223 @Override 224 public void onConversationVisibilityChanged(boolean visible) { 225 super.onConversationVisibilityChanged(visible); 226 if (!visible) { 227 mPagerController.hide(); 228 } 229 } 230 231 @Override 232 public void onConversationListVisibilityChanged(boolean visible) { 233 super.onConversationListVisibilityChanged(visible); 234 } 235 236 @Override 237 public void resetActionBarIcon() { 238 if (mViewMode.isListMode()) { 239 mActionBarView.removeBackButton(); 240 } else { 241 mActionBarView.setBackButton(); 242 } 243 } 244 245 /** 246 * Enable or disable the CAB mode based on the visibility of the conversation list fragment. 247 */ 248 private final void enableOrDisableCab() { 249 if (mLayout.isConversationListCollapsed()) { 250 disableCabMode(); 251 } else { 252 enableCabMode(); 253 } 254 } 255 256 @Override 257 public void showConversation(Conversation conversation) { 258 super.showConversation(conversation); 259 if (mActivity == null) { 260 return; 261 } 262 if (conversation == null) { 263 // This is a request to remove the conversation view and show the conversation list 264 // fragment instead. 265 onBackPressed(); 266 return; 267 } 268 // If conversation list is not visible, then the user cannot see the CAB mode, so exit it. 269 // This is needed here (in addition to during viewmode changes) because orientation changes 270 // while viewing a conversation don't change the viewmode: the mode stays 271 // ViewMode.CONVERSATION and yet the conversation list goes in and out of visibility. 272 enableOrDisableCab(); 273 274 final int mode = mViewMode.getMode(); 275 if (mode == ViewMode.SEARCH_RESULTS_LIST || mode == ViewMode.SEARCH_RESULTS_CONVERSATION) { 276 mViewMode.enterSearchResultsConversationMode(); 277 } else { 278 mViewMode.enterConversationMode(); 279 } 280 mPagerController.show(mAccount, mFolder, conversation); 281 final ConversationListFragment convList = getConversationListFragment(); 282 if (convList != null) { 283 LogUtils.d(LOG_TAG, "showConversation: Selecting position %d.", conversation.position); 284 convList.setSelected(conversation.position); 285 } 286 } 287 288 @Override 289 public void showWaitForInitialization() { 290 super.showWaitForInitialization(); 291 292 Fragment waitFragment = WaitFragment.newInstance(mAccount); 293 FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction(); 294 fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); 295 fragmentTransaction.replace(R.id.two_pane_activity, waitFragment, TAG_WAIT); 296 fragmentTransaction.commitAllowingStateLoss(); 297 } 298 299 @Override 300 public void hideWaitForInitialization() { 301 final FragmentManager manager = mActivity.getFragmentManager(); 302 final WaitFragment waitFragment = (WaitFragment)manager.findFragmentByTag(TAG_WAIT); 303 if (waitFragment != null) { 304 FragmentTransaction fragmentTransaction = 305 mActivity.getFragmentManager().beginTransaction(); 306 fragmentTransaction.remove(waitFragment); 307 fragmentTransaction.commitAllowingStateLoss(); 308 } 309 } 310 311 /** 312 * Up works as follows: 313 * 1) If the user is in a conversation and: 314 * a) the conversation list is hidden (portrait mode), shows the conv list and 315 * stays in conversation view mode. 316 * b) the conversation list is shown, goes back to conversation list mode. 317 * 2) If the user is in search results, up exits search. 318 * mode and returns the user to whatever view they were in when they began search. 319 * 3) If the user is in conversation list mode, there is no up. 320 */ 321 @Override 322 public boolean onUpPressed() { 323 int mode = mViewMode.getMode(); 324 if (mode == ViewMode.CONVERSATION) { 325 if (mLayout.isConversationListCollapsed()) { 326 commitLeaveBehindItems(); 327 } 328 mActivity.onBackPressed(); 329 } else if (mode == ViewMode.SEARCH_RESULTS_CONVERSATION) { 330 if (mLayout.isConversationListCollapsed() 331 || (mConvListContext.isSearchResult() && !Utils 332 .showTwoPaneSearchResults 333 (mActivity.getApplicationContext()))) { 334 commitLeaveBehindItems(); 335 onBackPressed(); 336 } else { 337 mActivity.finish(); 338 } 339 } else if (mode == ViewMode.SEARCH_RESULTS_LIST) { 340 mActivity.finish(); 341 } else if (mode == ViewMode.CONVERSATION_LIST) { 342 popView(true); 343 } 344 return true; 345 } 346 347 @Override 348 public boolean onBackPressed() { 349 // Clear any visible undo bars. 350 mUndoBarView.hide(false); 351 popView(false); 352 return true; 353 } 354 355 /** 356 * Pops the "view stack" to the last screen the user was viewing. 357 * 358 * @param preventClose Whether to prevent closing the app if the stack is empty. 359 */ 360 protected void popView(boolean preventClose) { 361 // If the user is in search query entry mode, or the user is viewing 362 // search results, exit 363 // the mode. 364 int mode = mViewMode.getMode(); 365 if (mode == ViewMode.SEARCH_RESULTS_LIST) { 366 mActivity.finish(); 367 } else if (mode == ViewMode.CONVERSATION) { 368 // Go to conversation list. 369 mViewMode.enterConversationListMode(); 370 } else if (mode == ViewMode.SEARCH_RESULTS_CONVERSATION) { 371 mViewMode.enterSearchResultsListMode(); 372 } else { 373 if (mode == ViewMode.CONVERSATION_LIST && getFolderListFragment().showingHierarchy()) { 374 // If the user navigated via the left folders list into a child folder, 375 // back should take the user up to the parent folder's conversation list. 376 if (mFolder.parent != null) { 377 goUpFolderHierarchy(mFolder); 378 } else { 379 // Show inbox; we are at the top of the hierarchy we were 380 // showing, and it doesn't have a parent, so we must want to 381 // the basic account folder list. 382 createFolderListFragment(null, mAccount.folderListUri); 383 loadAccountInbox(); 384 } 385 } else if (!preventClose) { 386 // There is nothing else to pop off the stack. 387 mActivity.finish(); 388 } 389 } 390 } 391 392 @Override 393 public void exitSearchMode() { 394 int mode = mViewMode.getMode(); 395 if (mode == ViewMode.SEARCH_RESULTS_LIST 396 || (mode == ViewMode.SEARCH_RESULTS_CONVERSATION 397 && Utils.showTwoPaneSearchResults(mActivity.getApplicationContext()))) { 398 mActivity.finish(); 399 } 400 } 401 402 @Override 403 public boolean shouldShowFirstConversation() { 404 return Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction()) 405 && Utils.showTwoPaneSearchResults(mActivity.getApplicationContext()); 406 } 407 408 @Override 409 public void onUndoAvailable(UndoOperation op) { 410 final int mode = mViewMode.getMode(); 411 final FrameLayout.LayoutParams params; 412 final ConversationListFragment convList = getConversationListFragment(); 413 switch (mode) { 414 case ViewMode.SEARCH_RESULTS_LIST: 415 case ViewMode.CONVERSATION_LIST: 416 params = (FrameLayout.LayoutParams) mUndoBarView.getLayoutParams(); 417 params.width = mLayout.computeConversationListWidth(); 418 params.gravity = Gravity.BOTTOM | Gravity.RIGHT; 419 mUndoBarView.setLayoutParams(params); 420 if (convList != null) { 421 mUndoBarView.show(true, mActivity.getActivityContext(), op, mAccount, 422 convList.getAnimatedAdapter(), mConversationListCursor); 423 } 424 break; 425 case ViewMode.SEARCH_RESULTS_CONVERSATION: 426 case ViewMode.CONVERSATION: 427 if (op.mBatch) { 428 // Show undo bar in the conversation list. 429 params = (FrameLayout.LayoutParams) mUndoBarView.getLayoutParams(); 430 params.gravity = Gravity.BOTTOM | Gravity.LEFT; 431 params.width = mLayout.computeConversationListWidth(); 432 } else { 433 // Show undo bar in the conversation. 434 params = (FrameLayout.LayoutParams) mUndoBarView.getLayoutParams(); 435 params.gravity = Gravity.BOTTOM | Gravity.RIGHT; 436 params.width = mLayout.getConversationView().getWidth(); 437 } 438 mUndoBarView.setLayoutParams(params); 439 mUndoBarView.show(true, mActivity.getActivityContext(), op, mAccount, 440 convList.getAnimatedAdapter(), mConversationListCursor); 441 break; 442 } 443 } 444} 445