Create TwoPaneLayout from TriStateSplitLayout

Minor changes to the OnePane and TwoPane Controllers to hook in the layout.

Change-Id: Ie3fc73ed3fb63441d7ed9b79ce02c2f1e9f51e36
diff --git a/res/layout/two_pane_activity.xml b/res/layout/two_pane_activity.xml
new file mode 100644
index 0000000..fa6e943
--- /dev/null
+++ b/res/layout/two_pane_activity.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2011 Google Inc.
+     Licensed to The Android Open Source Project.
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.mail.ui.TwoPaneLayout
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  android:id="@+id/two_pane_activity"
+  android:orientation="horizontal"
+  android:splitMotionEvents="true"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent" >
+
+  <FrameLayout
+    android:id="@+id/labels_pane"
+    android:layout_width="@dimen/label_list_pane_width"
+    android:layout_height="match_parent"/>
+
+  <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:id="@+id/conversation_column_container">
+
+      <RelativeLayout
+        android:id="@+id/conversation_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <FrameLayout
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"
+          android:id="@+id/conversation_list_pane"
+          android:layout_alignParentTop="true" />
+
+      </RelativeLayout>
+
+  </RelativeLayout>
+
+  <FrameLayout
+    android:id="@+id/conversation_pane_container"
+    android:layout_width="0dip"
+    android:layout_height="match_parent"
+    android:visibility="gone"
+    android:background="@android:color/white">
+
+    <FrameLayout
+        android:id="@+id/conversation_pane"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <View
+      android:id="@+id/conversation_overlay"
+      android:background="@android:color/transparent"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:visibility="gone" />
+
+    <TextView
+        android:id="@+id/conversation_error"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:text="@string/no_conversations"
+        android:gravity="center"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:visibility="gone"
+        android:background="@android:color/white"/>
+
+  </FrameLayout>
+</com.android.mail.ui.TwoPaneLayout>
+
diff --git a/res/values/animation_constants.xml b/res/values/animation_constants.xml
index a50e6c8..3ed2bc1 100644
--- a/res/values/animation_constants.xml
+++ b/res/values/animation_constants.xml
@@ -16,6 +16,9 @@
      limitations under the License.
 -->
 <resources>
+    <integer name="activity_slide_left_duration">475</integer>
+    <integer name="activity_slide_right_duration">525</integer>
+    <integer name="activity_collapse_duration">125</integer>
     <integer name="expand_cc_bcc_dur">150</integer>
     <integer name="fadein_cc_bcc_dur">150</integer>
     <integer name="fade_duration">250</integer>
diff --git a/res/values/constants.xml b/res/values/constants.xml
index cac8683..3b1c677 100644
--- a/res/values/constants.xml
+++ b/res/values/constants.xml
@@ -16,7 +16,7 @@
      limitations under the License.
 -->
 <resources>
-    <!-- Boolean value indicating whether the table UI should be used. -->
+    <!-- Boolean value indicating whether the tablet UI should be used. -->
     <integer name="use_tablet_ui">0</integer>
     <integer name="conversation_list_header_mode">1</integer>
     <integer name="conversation_header_mode">1</integer>
@@ -26,4 +26,17 @@
 
     <!-- Frequency (in milliseconds) for the refresh of timestamps in conversation list fragments. -->
     <integer name="timestamp_update_interval">60000</integer>
+
+    <!-- Boolean value indicating whether the conversation list be
+         removed from the UI. This is something only tablets can do,
+         so false in the default case.  Set this true for tablets. -->
+    <integer name="conversation_list_collapsible">0</integer>
+    <!-- Relative weights of the different views.  These three
+         determine how much of the screen width is taken by the
+         different views. For the default layout (phone), these values
+         are equal. These are only used in TwoPaneLayout, so they need
+         to be specified for sw600dp and sw720dp layouts. -->
+    <integer name="label_list_weight">1</integer>
+    <integer name="conversation_list_weight">1</integer>
+    <integer name="conversation_view_weight">1</integer>
 </resources>
diff --git a/src/com/android/mail/ConversationListContext.java b/src/com/android/mail/ConversationListContext.java
index 6d27fbe..ce5e927 100644
--- a/src/com/android/mail/ConversationListContext.java
+++ b/src/com/android/mail/ConversationListContext.java
@@ -85,7 +85,7 @@
      * Builds a context for a view to a Gmail folder. Note that folder may be null, in which case
      * the context defaults to a view of the inbox.
      */
-    private static ConversationListContext forFolder(
+    public static ConversationListContext forFolder(
             Context context, Account account, String folder) {
         // Mock stuff for now.
         return new ConversationListContext(account, null, folder);
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index 9b32531..92b87cf 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -31,6 +31,7 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MotionEvent;
+import android.view.Window;
 import android.widget.LinearLayout;
 
 import com.android.mail.R;
@@ -58,12 +59,18 @@
     private static final String SAVED_CONVERSATIONS = "saved-conversations";
     // Keys for serialization of various information in Bundles.
     private static final String SAVED_LIST_CONTEXT = "saved-list-context";
-    private Account mAccount;
-    private ActionBarView mActionBarView;
+    /**
+     * Are we on a tablet device or not.
+     */
+    public final boolean IS_TABLET_DEVICE;
 
+    protected Account mAccount;
+    protected ActionBarView mActionBarView;
     protected final RestrictedActivity mActivity;
     private ConversationSelectionSet mBatchConversations = new ConversationSelectionSet();
     protected final Context mContext;
+    protected ConversationListContext mConversationListContext;
+
     protected ConversationListFragment mConversationListFragment;
     /**
      * The current mode of the application. All changes in mode are initiated by the activity
@@ -72,16 +79,11 @@
      */
     protected final ViewMode mViewMode;
 
-    /**
-     * Are we on a tablet device or not.
-     */
-    protected final boolean mTabletDevice;
-
     public AbstractActivityController(MailActivity activity, ViewMode viewMode) {
         mActivity = activity;
         mViewMode = viewMode;
         mContext = activity.getApplicationContext();
-        mTabletDevice = Utils.useTabletUI(mContext);
+        IS_TABLET_DEVICE = Utils.useTabletUI(mContext);
     }
 
     @Override
@@ -143,6 +145,11 @@
     }
 
     @Override
+    public int getMode() {
+        return mViewMode.getMode();
+    }
+
+    @Override
     public String getUnshownSubject(String subject) {
         // Calculate how much of the subject is shown, and return the remaining.
         return null;
@@ -242,12 +249,16 @@
     }
 
     @Override
-    public void onCreate(Bundle savedState) {
+    public boolean onCreate(Bundle savedState) {
+        // Request opaque actionbar
+        mActivity.getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
+
         // Initialize the action bar view.
         initCustomActionBarView();
 
         final Intent intent = mActivity.getIntent();
         // TODO(viki) Choose an account here.
+        // If we cannot choose an account, we return false
 
         // Allow shortcut keys to function for the ActionBar and menus.
         mActivity.setDefaultKeyMode(Activity.DEFAULT_KEYS_SHORTCUT);
@@ -255,6 +266,7 @@
 
         mViewMode.addListener(this);
         restoreState(savedState);
+        return true;
     }
 
     @Override
@@ -487,5 +499,4 @@
         // TODO(viki): Auto-generated method stub
 
     }
-
 }
diff --git a/src/com/android/mail/ui/ActivityController.java b/src/com/android/mail/ui/ActivityController.java
index 8bc1dec..8efc290 100644
--- a/src/com/android/mail/ui/ActivityController.java
+++ b/src/com/android/mail/ui/ActivityController.java
@@ -26,6 +26,7 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 
+import com.android.mail.ConversationListContext;
 import com.android.mail.ui.ViewMode.ModeChangeListener;
 
 /**
@@ -67,6 +68,13 @@
     ConversationSelectionSet getBatchConversations();
 
     /**
+     * Return the current mode the activity is in. Values need to be matched against constants in
+     * {@link ViewMode}.
+     * @return
+     */
+    int getMode();
+
+    /**
      *
      */
     void handleConversationLoadError();
@@ -103,13 +111,16 @@
 
     /**
      * Called when the root activity calls onCreate. Any initialization needs to
-     * be done here. This was called initialize in Gmail. Nothing. Otherwise
-     * does nothing.
+     * be done here. Subclasses need to call their parents' onCreate method, since it performs
+     * valuable initialization common to all subclasses.
+     *
+     * This was called initialize in Gmail.
      *
      * @see android.app.Activity#onCreate
      * @param savedState
+     * @return true if the controller was able to initialize successfully, false otherwise.
      */
-    void onCreate(Bundle savedState);
+    boolean onCreate(Bundle savedState);
 
     /**
      * @see android.app.Activity#onCreateDialog(int, Bundle)
@@ -194,4 +205,17 @@
      */
     void onWindowFocusChanged(boolean hasFocus);
 
+    /**
+     * Set the Action Bar icon according to the mode. The Action Bar icon can contain a back button
+     * or not. The individual controller is responsible for changing the icon based on the mode.
+     */
+    void resetActionBarIcon();
+
+    /**
+     * Show the conversation List with the list context provided here. On certain layouts, this
+     * might show more than just the conversation list. For instance, on tablets this might show
+     * the conversations along with the conversation list.
+     * @param listContext context providing information on what conversation list to display.
+     */
+    void showConversationList(ConversationListContext listContext);
 }
diff --git a/src/com/android/mail/ui/ControllableActivity.java b/src/com/android/mail/ui/ControllableActivity.java
index 18b0d7b..067080a 100644
--- a/src/com/android/mail/ui/ControllableActivity.java
+++ b/src/com/android/mail/ui/ControllableActivity.java
@@ -17,6 +17,8 @@
 
 package com.android.mail.ui;
 
+import com.android.mail.ui.ViewMode.ModeChangeListener;
+
 
 /**
  * A controllable activity is an Activity that has a Controller attached. This activity must be
@@ -24,6 +26,15 @@
  */
 public interface ControllableActivity extends HelpCallback, RestrictedActivity {
     /**
+     * Attaches the conversation list fragment to the activity controller. This callback is
+     * currently required because the Activity Controller directly calls methods on the conversation
+     * list fragment. This behavior should be modified to allow the controller to call a layout
+     * controller which knows about the fragments.
+     * @param conversationList
+     */
+    void attachConversationList(ConversationListFragment conversationList);
+
+    /**
      * Returns the conversations selected by the user for performing a batch action like archive,
      * delete, etc.
      * @return conversations selected
@@ -35,15 +46,17 @@
      * @see com.android.mail.ui.ViewMode
      * @return the mode the activity is currently in.
      */
-    ViewMode getViewMode();
+    int getViewMode();
 
     /**
-     * Attaches the conversation list fragment to the activity controller. This callback is
-     * currently required because the Activity Controller directly calls methods on the conversation
-     * list fragment. This behavior should be modified to allow the controller to call a layout
-     * controller which knows about the fragments.
-     * @param conversationList
+     * Sets the listener for receiving ViewMode changes.
+     * @param listener
      */
-    void attachConversationList(ConversationListFragment conversationList);
+    void setViewModeListener(ModeChangeListener listener);
 
+    /**
+     * Removes the given listener from receiving ViewMode changes.
+     * @param listener
+     */
+    void unsetViewModeListener(ModeChangeListener listener);
 }
diff --git a/src/com/android/mail/ui/ConversationListFragment.java b/src/com/android/mail/ui/ConversationListFragment.java
index 1e29a61..3ec5bf4 100644
--- a/src/com/android/mail/ui/ConversationListFragment.java
+++ b/src/com/android/mail/ui/ConversationListFragment.java
@@ -56,6 +56,9 @@
 
     private static final String LOG_TAG = new LogUtils().getLogTag();
 
+    // True if we are on a tablet device
+    private static boolean mTabletDevice;
+
     /**
      * Frequency of update of timestamps. Initialized in {@link #onCreate(Bundle)} and final
      * afterwards.
@@ -65,16 +68,13 @@
     private ControllableActivity mActivity;
 
     private boolean mAnimateChanges;
-
     // Control state.
     private ConversationListCallbacks mCallbacks;
-    private View mEmptyView;
 
+    private View mEmptyView;
     private final Handler mHandler = new Handler();
     // True if the view is in CAB (Contextual Action Bar: some conversations are selected) mode
     private boolean mIsCabMode;
-    // True if we are on a tablet device
-    private static boolean mTabletDevice;
 
     // List save state.
     private Parcelable mListSavedState;
@@ -97,9 +97,6 @@
 
     private ConversationListContext mViewContext;
 
-    // Which mode is the underlying controller in?
-    private ViewMode mViewMode;
-
     /**
      * Creates a new instance of {@link ConversationListFragment}, initialized to display
      * conversation list context.
@@ -118,12 +115,8 @@
     private void bindActivityInfo() {
         final Activity activity = getActivity();
         mCallbacks = (ConversationListCallbacks) activity;
-        mViewMode = mActivity.getViewMode();
         StarHandler starHandler = (StarHandler) activity;
-        if (mViewMode != null) {
-            mViewMode.addListener(this);
-        }
-
+        mActivity.setViewModeListener(this);
         mActivity.getBatchConversations().addObserver(this);
 
         // TODO(mindyp): find some way to make the notification container more re-usable.
@@ -172,7 +165,7 @@
 
         // The onViewModeChanged callback doesn't get called when the mode object is created, so
         // force setting the mode manually this time around.
-        onViewModeChanged(mViewMode.getMode());
+        onViewModeChanged(mActivity.getViewMode());
 
         if (mActivity.isFinishing()) {
             // Activity is finishing, just bail.
@@ -239,7 +232,7 @@
         // Clear the adapter.
         mListView.setAdapter(null);
 
-        mViewMode.removeListener(this);
+        mActivity.unsetViewModeListener(this);
         mActivity.attachConversationList(null);
         mActivity.getBatchConversations().removeObserver(this);
 
@@ -315,7 +308,6 @@
     @Override
     public void onViewModeChanged(int newMode) {
         // Change the divider based on view mode.
-        Resources res = getView().getResources();
         if (mTabletDevice) {
             if (newMode == ViewMode.CONVERSATION) {
                 mListView.setBackgroundResource(R.drawable.panel_conversation_leftstroke);
@@ -329,7 +321,6 @@
         }
     }
 
-
     /**
      * Visually refresh the conversation list. This does not start a sync.
      */
@@ -355,7 +346,6 @@
         }
     }
 
-
     /**
      * View the message at the given position.
      * @param position
diff --git a/src/com/android/mail/ui/FolderSettingsObservable.java b/src/com/android/mail/ui/FolderSettingsObservable.java
new file mode 100644
index 0000000..44983ab
--- /dev/null
+++ b/src/com/android/mail/ui/FolderSettingsObservable.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ *      Copyright (C) 2012 Google Inc.
+ *      Licensed to The Android Open Source Project.
+ *
+ *      Licensed under the Apache License, Version 2.0 (the "License");
+ *      you may not use this file except in compliance with the License.
+ *      You may obtain a copy of the License at
+ *
+ *           http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *      Unless required by applicable law or agreed to in writing, software
+ *      distributed under the License is distributed on an "AS IS" BASIS,
+ *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *      See the License for the specific language governing permissions and
+ *      limitations under the License.
+ *******************************************************************************/
+
+package com.android.mail.ui;
+
+import java.util.ArrayList;
+
+/**
+ * Represents folder settings and allows observers to listen for changes.
+ *
+ */
+public interface FolderSettingsObservable {
+    void registerObserver(FolderSettingsObserver observer);
+    void unregisterObserver(FolderSettingsObserver observer);
+    void notifyChanged();
+    ArrayList<String> getIncludedLabels();
+    ArrayList<String> getPartialLabels();
+    int getNumberOfSyncDays();
+    void setIncludedLabels(ArrayList<String> labels);
+    void setPartialLabels(ArrayList<String> labels);
+}
diff --git a/src/com/android/mail/ui/FolderSettingsObserver.java b/src/com/android/mail/ui/FolderSettingsObserver.java
new file mode 100644
index 0000000..df301cf
--- /dev/null
+++ b/src/com/android/mail/ui/FolderSettingsObserver.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ *      Copyright (C) 2012 Google Inc.
+ *      Licensed to The Android Open Source Project.
+ *
+ *      Licensed under the Apache License, Version 2.0 (the "License");
+ *      you may not use this file except in compliance with the License.
+ *      You may obtain a copy of the License at
+ *
+ *           http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *      Unless required by applicable law or agreed to in writing, software
+ *      distributed under the License is distributed on an "AS IS" BASIS,
+ *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *      See the License for the specific language governing permissions and
+ *      limitations under the License.
+ *******************************************************************************/
+
+package com.android.mail.ui;
+
+/**
+ * Observer to be notified when folder settings changed.
+ */
+public interface FolderSettingsObserver {
+    void onChanged();
+}
diff --git a/src/com/android/mail/ui/MailActivity.java b/src/com/android/mail/ui/MailActivity.java
index c2d912d..528b503 100644
--- a/src/com/android/mail/ui/MailActivity.java
+++ b/src/com/android/mail/ui/MailActivity.java
@@ -26,6 +26,7 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 
+import com.android.mail.ui.ViewMode.ModeChangeListener;
 import com.android.mail.utils.Utils;
 
 
@@ -50,9 +51,6 @@
      */
     private boolean mLaunchedCleanly = false;
 
-    /**
-     * The specific view mode that we are in.
-     */
     private ViewMode mViewMode;
 
     @Override
@@ -79,8 +77,8 @@
      * Default implementation returns a null view mode.
      */
     @Override
-    public ViewMode getViewMode() {
-        return mViewMode;
+    public int getViewMode() {
+        return mViewMode.getMode();
     }
 
     @Override
@@ -205,4 +203,14 @@
         super.onWindowFocusChanged(hasFocus);
         mController.onWindowFocusChanged(hasFocus);
     }
+
+    @Override
+    public void setViewModeListener(ModeChangeListener listener) {
+        mViewMode.addListener(listener);
+    }
+
+    @Override
+    public void unsetViewModeListener(ModeChangeListener listener) {
+        mViewMode.removeListener(listener);
+    }
 }
diff --git a/src/com/android/mail/ui/OnePaneController.java b/src/com/android/mail/ui/OnePaneController.java
index c096b5d..07625c2 100644
--- a/src/com/android/mail/ui/OnePaneController.java
+++ b/src/com/android/mail/ui/OnePaneController.java
@@ -17,18 +17,25 @@
 
 package com.android.mail.ui;
 
+import android.app.Fragment;
+import android.app.FragmentTransaction;
 import android.os.Bundle;
+import android.text.TextUtils;
 import android.view.Window;
 
+import com.android.mail.ConversationListContext;
 import com.android.mail.R;
 
 /**
  * Controller for one-pane Mail activity. One Pane is used for phones, where screen real estate is
- * limited.
+ * limited. This controller also does the layout, since the layout is simpler in the one pane case.
  */
 
 // Called OnePaneActivityController in Gmail.
 public final class OnePaneController extends AbstractActivityController {
+
+    private ConversationListContext mConversationListContext;
+
     /**
      * @param activity
      * @param viewMode
@@ -39,13 +46,22 @@
     }
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        // Request opaque actionbar
-        mActivity.getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
+    public void resetActionBarIcon() {
+        final int mode = mViewMode.getMode();
+        if ((mode == ViewMode.CONVERSATION_LIST && mConversationListContext.isSearchResult())
+                || mode == ViewMode.CONVERSATION || mode == ViewMode.FOLDER_LIST) {
+            mActionBarView.setBackButton();
+        } else {
+            mActionBarView.removeBackButton();
+        }
+    }
+
+    @Override
+    public boolean onCreate(Bundle savedInstanceState) {
         // Set 1-pane content view.
         mActivity.setContentView(R.layout.one_pane_activity);
-
-        super.onCreate(savedInstanceState);
+        // The parent class sets the correct viewmode and starts the application off.
+        return super.onCreate(savedInstanceState);
     }
 
     @Override
@@ -53,4 +69,42 @@
         // TODO(viki): Auto-generated method stub
         return false;
     }
+
+    @Override
+    public void onViewModeChanged(int newMode) {
+        super.onViewModeChanged(newMode);
+        if (newMode == ViewMode.CONVERSATION_LIST) {
+            // TODO(viki): Get name of inbox from the Account
+            final String inboxFolder = "inbox";
+            ConversationListContext listContext = ConversationListContext.forFolder(mContext,
+                    mAccount, inboxFolder);
+            showConversationList(listContext);
+        }
+
+        // We don't want to invalidate the options menu when switching to conversation
+        // mode, as it will happen when the conversation finishes loading.
+        if (newMode != ViewMode.CONVERSATION) {
+            mActivity.invalidateOptionsMenu();
+        }
+    }
+
+    @Override
+    public void showConversationList(ConversationListContext listContext) {
+        FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
+        fragmentTransaction.addToBackStack(null);
+        final boolean accountChanged = false;
+        // TODO(viki): This account transition looks strange in two pane mode. Revisit as the app
+        // is coming together and improve the look and feel.
+        final int transition = accountChanged ? FragmentTransaction.TRANSIT_FRAGMENT_FADE :
+                FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
+        fragmentTransaction.setTransition(transition);
+
+        Fragment conversationListFragment = ConversationListFragment
+                .newInstance(mConversationListContext);
+        fragmentTransaction.replace(R.id.content_pane, conversationListFragment);
+
+        fragmentTransaction.commitAllowingStateLoss();
+        resetActionBarIcon();
+    }
+
 }
diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java
index 56510ee..1874ed9 100644
--- a/src/com/android/mail/ui/TwoPaneController.java
+++ b/src/com/android/mail/ui/TwoPaneController.java
@@ -17,6 +17,14 @@
 
 package com.android.mail.ui;
 
+import com.android.mail.ConversationListContext;
+import com.android.mail.R;
+
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.view.Window;
+
 
 /**
  * Controller for one-pane Mail activity. One Pane is used for phones, where screen real estate is
@@ -25,6 +33,8 @@
 
 // Called OnePaneActivityController in Gmail.
 public final class TwoPaneController extends AbstractActivityController {
+    private boolean mJumpToFirstConversation;
+    private TwoPaneLayout mLayout;
 
     /**
      * @param activity
@@ -35,10 +45,72 @@
         // TODO(viki): Auto-generated constructor stub
     }
 
+    /**
+     * Display the conversation list fragment.
+     * @param show
+     */
+    private void initializeConversationListFragment(boolean show) {
+        if (show) {
+            mViewMode.enterConversationListMode();
+        }
+        FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
+        // Use cross fading animation.
+        fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
+        Fragment conversationListFragment = ConversationListFragment
+                .newInstance(mConversationListContext);
+        fragmentTransaction.replace(R.id.conversation_list_pane, conversationListFragment);
+        fragmentTransaction.commitAllowingStateLoss();
+    }
+
     @Override
     protected boolean isConversationListVisible() {
         // TODO(viki): Auto-generated method stub
         return false;
     }
 
+    @Override
+    public void showConversationList(ConversationListContext context) {
+        initializeConversationListFragment(true);
+    }
+
+    @Override
+    public boolean onCreate(Bundle savedState) {
+        mActivity.setContentView(R.layout.two_pane_activity);
+
+        mLayout = (TwoPaneLayout) mActivity.findViewById(R.id.two_pane_activity);
+        mLayout.initializeLayout(mActivity.getApplicationContext());
+
+        // The tablet layout needs to refer to mode changes.
+        mViewMode.addListener(mLayout);
+        // The activity controller needs to listen to layout changes.
+        mLayout.setListener(this);
+
+        final boolean isParentInitialized = super.onCreate(savedState);
+        if (isParentInitialized && savedState == null) {
+            // Show a Label list, in the real application
+            // renderLabelList();
+            // In our case, we show a conversation list for everything.
+        }
+        return isParentInitialized;
+    }
+
+    @Override
+    public void onViewModeChanged(int newMode) {
+        super.onViewModeChanged(newMode);
+        if (newMode != ViewMode.CONVERSATION) {
+            // Clear this flag if the user jumps out of conversation mode
+            // before a load completes.
+            mJumpToFirstConversation = false;
+        }
+    }
+
+    @Override
+    public void resetActionBarIcon() {
+        if (mViewMode.getMode() == ViewMode.CONVERSATION_LIST) {
+            mActionBarView.removeBackButton();
+        } else {
+            mActionBarView.setBackButton();
+        }
+    }
+
 }
diff --git a/src/com/android/mail/ui/TwoPaneLayout.java b/src/com/android/mail/ui/TwoPaneLayout.java
new file mode 100644
index 0000000..4529081
--- /dev/null
+++ b/src/com/android/mail/ui/TwoPaneLayout.java
@@ -0,0 +1,818 @@
+/*******************************************************************************
+ *      Copyright (C) 2012 Google Inc.
+ *      Licensed to The Android Open Source Project.
+ *
+ *      Licensed under the Apache License, Version 2.0 (the "License");
+ *      you may not use this file except in compliance with the License.
+ *      You may obtain a copy of the License at
+ *
+ *           http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *      Unless required by applicable law or agreed to in writing, software
+ *      distributed under the License is distributed on an "AS IS" BASIS,
+ *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *      See the License for the specific language governing permissions and
+ *      limitations under the License.
+ *******************************************************************************/
+
+package com.android.mail.ui;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.LinearLayout;
+
+import com.android.mail.R;
+import com.android.mail.ui.ViewMode.ModeChangeListener;
+import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
+
+import java.util.ArrayList;
+
+/**
+ * This is a custom layout that manages the possible views of Gmail's large screen (read: tablet)
+ * activity, and the transitions between them.
+ *
+ * This is not intended to be a generic layout; it is specific to the {@code Fragment}s
+ * available in {@link MailActivity} and assumes their existence. It merely configures them
+ * according to the specific <i>modes</i> the {@link Activity} can be in.
+ *
+ * Currently, the layout differs in three dimensions: orientation, two aspects of view modes.
+ * This results in essentially three states: One where the labels are on the left and conversation
+ * list is on the right, and two states where the conversation list is on the left: one in which
+ * it's collapsed and another where it is not.
+ *
+ * In Label or conversation list view, conversations are hidden and labels and conversation lists
+ * are visible. This is the case in both portrait and landscape
+ *
+ * In Conversation List or Conversation View, labels are hidden, and conversation lists and
+ * conversation view is visible. This is the case in both portrait and landscape.
+ *
+ * In the Gmail source code, this was called TriStateSplitLayout
+ */
+final class TwoPaneLayout extends LinearLayout
+        implements ModeChangeListener, OnTouchListener {
+
+    /**
+     * Scaling modifier for sAnimationSlideRightDuration.
+     */
+    private static final double SLIDE_DURATION_SCALE = 2.0 / 3.0;
+    private static final String LOG_TAG = new LogUtils().getLogTag();
+    private static final TimeInterpolator sCollapseInterpolator = new DecelerateInterpolator(2.5f);
+    private static final TimeInterpolator sLeftInterpolator = new DecelerateInterpolator(2.25f);
+    private static final TimeInterpolator sRightInterpolator = new DecelerateInterpolator(2.5f);
+
+    private static int sAnimationCollapseDuration;
+    private static int sAnimationSlideLeftDuration;
+    private static int sAnimationSlideRightDuration;
+    private static double sScaledConversationListWeight;
+    private static double sScaledLabelListWeight;
+    /**
+     * The current mode that the tablet layout is in. This is a constant integer that holds values
+     * that are {@link ViewMode} constants like {@link ViewMode#CONVERSATION}.
+     */
+    private int currentMode;
+    /**
+     * Whether or not the layout is currently in the middle of a cross-fade animation that requires
+     * custom rendering.
+     */
+    private boolean mAnimatingFade;
+
+    private Context mContext;
+    private int mConversationLeft;
+
+    private View mConversationListContainer;
+
+    private View mConversationView;
+    private View mConversationViewOverlay;
+    /** Left position of each fragment. */
+    private int mLabelsLeft;
+    private View mLabelsView;
+    private int mListAlpha;
+
+    /** Captured bitmap of each fragment. */
+    private Bitmap mListBitmap;
+    private int mListBitmapLeft;
+    /** Whether or not the conversation list can be collapsed all the way to hidden on the left.
+     * This is used only in portrait view*/
+    private boolean mListCollapsed;
+    private LayoutListener mListener;
+    private int mListLeft;
+    private Paint mListPaint;
+    private View mListView;
+    /**
+     * A handle to any out standing animations that are in progress.
+     */
+    private Animator mOutstandingAnimator;
+
+    /** Paint to be used for each fragment. */
+    private Paint mPaint;
+
+    private final AnimatorListener mConversationListListener =
+            new AnimatorListener(AnimatorListener.CONVERSATION_LIST);
+    private final AnimatorListener mCollapseListListener =
+            new AnimatorListener(AnimatorListener.COLLAPSE_LIST);
+    private final AnimatorListener mConversationListener =
+            new AnimatorListener(AnimatorListener.CONVERSATION);
+    private final AnimatorListener mUncollapseListListener =
+            new AnimatorListener(AnimatorListener.UNCOLLAPSE_LIST);
+
+    private class AnimatorListener implements Animator.AnimatorListener {
+        public static final int CONVERSATION_LIST = 1;
+        public static final int COLLAPSE_LIST = 2;
+        public static final int CONVERSATION = 3;
+        public static final int UNCOLLAPSE_LIST = 4;
+
+        /**
+         * Different animator listeners need to perform different actions on start and finish based
+         * on their type. The types are assigned at object creation using only the constants:
+         * {@link #CONVERSATION_LIST}, {@link #COLLAPSE_LIST}, {@link #CONVERSATION} or
+         * {@link #UNCOLLAPSE_LIST}
+         */
+        private final int listener_type;
+
+        /**
+         * Create an animator listener of a specific type. The types are created using the constants
+         * {@link #CONVERSATION_LIST}, {@link #COLLAPSE_LIST}, {@link #CONVERSATION} or
+         * {@link #UNCOLLAPSE_LIST}
+         * @param type
+         */
+        AnimatorListener(int type){
+            this.listener_type = type;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            LogUtils.d(LOG_TAG, "Cancelling animation (this=%s)", animation);
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mAnimatingFade = false;
+            mOutstandingAnimator = null;
+            destroyBitmaps();
+            // Now close the animation depending on the type of animator selected.
+            switch (listener_type) {
+                case CONVERSATION_LIST:
+                    onFinishEnteringConversationListMode();
+                    return;
+                case COLLAPSE_LIST:
+                    onCollapseList();
+                    return;
+                case CONVERSATION:
+                    onFinishEnteringConversationMode();
+                    return;
+                case UNCOLLAPSE_LIST:
+                    onUncollapseList();
+                    return;
+            }
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+            // Do nothing.
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            switch (listener_type) {
+                case CONVERSATION_LIST:
+                    mLabelsView.setVisibility(View.VISIBLE);
+            }
+        }
+    }
+
+    public TwoPaneLayout(Context context) {
+        super(context);
+    }
+
+    public TwoPaneLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public TwoPaneLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    /**
+     * Sets the {@link ViewMode} that this layout is synchronized to.
+     * @param viewMode The view mode object to listen to changes on.
+     */
+    // TODO(viki): Change this to have the ActivityController provide the viewMode only for adding
+    // as a listener.
+    public void attachToViewMode(ViewMode viewMode) {
+        viewMode.addListener(this);
+        currentMode = viewMode.getMode();
+    }
+
+    /**
+     * Captures list view.
+     */
+    private void captureListBitmaps() {
+        if (mListBitmap != null || mListView == null || mListView.getWidth() == 0
+                || mListView.getHeight() == 0) {
+            return;
+        }
+
+        try {
+            mListBitmap = Bitmap.createBitmap(mListView.getWidth(), mListView.getHeight(),
+                    Config.ARGB_8888);
+            Canvas canvas = new Canvas(mListBitmap);
+            mListView.draw(canvas);
+        } catch (OutOfMemoryError e) {
+            LogUtils.e(LOG_TAG, e, "Could not create a bitmap due to OutOfMemoryError");
+        }
+    }
+
+    /**
+     * Collapses the conversation list to the left if it is in an expanded state.
+     * Only applies in portrait mode.
+     */
+    public boolean collapseList() {
+        if (mListCollapsed) {
+            return false;
+        }
+        mListCollapsed = true;
+
+        PropertyValuesHolder listLeftValues = PropertyValuesHolder.ofInt(
+                "conversationListLeft",
+                getConversationListLeft(),
+                computeConversationListLeft(computeConversationListWidth()));
+
+        startLayoutAnimation(sAnimationCollapseDuration, mCollapseListListener,
+                sCollapseInterpolator, listLeftValues);
+        return true;
+    }
+
+    /**
+     * Computes left position of the conversation list relative to its uncollapsed position.
+     * This is only relevant in a collapsible view, and will be 0 otherwise.
+     */
+    private int computeConversationListLeft(int width) {
+        if (isConversationListCollapsible()) {
+            return mListCollapsed ? -width : 0;
+
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Computes the width of the conversation list in stable state of the current mode.
+     */
+    private int computeConversationListWidth() {
+        return computeConversationListWidth(getMeasuredWidth());
+    }
+
+    /**
+     * Computes the width of the conversation list in stable state of the current mode.
+     */
+    private int computeConversationListWidth(int totalWidth) {
+        switch (currentMode) {
+            case ViewMode.CONVERSATION_LIST:
+                return totalWidth - computeLabelListWidth();
+            case ViewMode.CONVERSATION:
+                return (int) (totalWidth * sScaledConversationListWeight);
+        }
+        return 0;
+    }
+
+    /**
+     * Computes the width of the conversation pane in stable state of the current mode.
+     */
+    private int computeConversationWidth() {
+        return computeConversationWidth(getMeasuredWidth());
+    }
+
+    /**
+     * Computes the width of the conversation pane in stable state of the
+     * current mode.
+     */
+    private int computeConversationWidth(int totalWidth) {
+        switch (currentMode) {
+            case ViewMode.CONVERSATION:
+                // Fallthrough
+            case ViewMode.SEARCH_RESULTS:
+                if (isConversationListCollapsible()) {
+                    return totalWidth;
+                }
+                return totalWidth - (int) (totalWidth * sScaledConversationListWeight);
+        }
+        return 0;
+    }
+
+    /**
+     * Computes the width of the label list in stable state of the current mode.
+     */
+    private int computeLabelListWidth() {
+        return computeLabelListWidth(getMeasuredWidth());
+    }
+
+    /**
+     * Computes the width of the label list in stable state of the current mode.
+     */
+    private int computeLabelListWidth(int totalWidth) {
+        return (int) (totalWidth * sScaledLabelListWeight);
+    }
+
+    /**
+     * Frees up the bitmaps.
+     */
+    private void destroyBitmaps() {
+        if (mListBitmap != null) {
+            mListBitmap.recycle();
+            mListBitmap = null;
+        }
+    }
+
+    private void dispatchConversationListVisibilityChange() {
+        if (mListener != null) {
+            // Post the visibility change using a handler, so other views
+            // will not be modified while we are performing a layout of the
+            // TriStateSplitLayout
+            final Handler handler = new Handler();
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onConversationListVisibilityChanged(isConversationListVisible());
+                }
+            });
+        }
+    }
+
+    private void dispatchConversationVisibilityChanged(boolean visible) {
+        if (mListener != null) {
+            mListener.onConversationVisibilityChanged(visible);
+        }
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        if (!isAnimatingFade()) {
+            super.dispatchDraw(canvas);
+            return;
+        }
+
+        canvas.save();
+        canvas.translate(mLabelsLeft, 0);
+        mLabelsView.draw(canvas);
+        canvas.restore();
+
+        // The bitmap can be null if the view hasn't been drawn by the time we capture the bitmap.
+        if (mListBitmap != null) {
+            canvas.drawBitmap(mListBitmap, mListBitmapLeft, 0, mListPaint);
+        }
+
+        canvas.saveLayerAlpha(mListLeft, 0, mListLeft + mListView.getWidth(), getHeight(),
+                mListAlpha, Canvas.ALL_SAVE_FLAG);
+        canvas.translate(mListLeft, 0);
+        mListView.draw(canvas);
+        canvas.restore();
+
+        canvas.save();
+        canvas.translate(mConversationLeft, 0);
+        mConversationView.draw(canvas);
+        canvas.restore();
+    }
+
+    private void enterConversationListMode() {
+        mListView.setPadding(mListView.getPaddingLeft(), 0, mListView.getPaddingRight(),
+                mListView.getPaddingBottom());
+
+        // On the initial call, measurements may not have been done (i.e. this
+        // Layout has never been rendered), so no animation will be done.
+        if (getMeasuredWidth() == 0) {
+            mLabelsView.setVisibility(View.VISIBLE);
+            onFinishEnteringConversationListMode();
+            return;
+        }
+
+        // Slide labels in from the left.
+        setLabelListWidth(computeLabelListWidth());
+
+        // Prepare animation.
+        mAnimatingFade = true;
+        captureListBitmaps();
+        ArrayList<PropertyValuesHolder> values = Lists.newArrayList();
+
+        int labelsWidth = computeLabelListWidth();
+        values.add(PropertyValuesHolder.ofInt("labelsLeft", -labelsWidth, 0));
+
+        // Reset the relative left of the list view.
+        setConversationListLeft(0);
+
+        // Push conversation list out to fill remaining space.
+        setConversationListWidth(computeConversationListWidth());
+
+        // Fading out the conversation bitmap should finish before
+        // the final transition to the conversation list view.
+        ObjectAnimator animator = ObjectAnimator.ofInt(this, "listBitmapAlpha", 255, 0);
+        animator.setDuration((long) (sAnimationSlideRightDuration * SLIDE_DURATION_SCALE));
+        animator.setInterpolator(sRightInterpolator);
+
+        values.add(PropertyValuesHolder.ofInt("listBitmapLeft", 0, labelsWidth));
+        values.add(PropertyValuesHolder.ofInt("listLeft", 0, labelsWidth));
+        values.add(PropertyValuesHolder.ofInt("listAlpha", 0, 255));
+
+        // Slide conversation out to the right.
+        values.add(PropertyValuesHolder.ofInt("conversationLeft", mListView.getMeasuredWidth(),
+                getWidth()));
+        ObjectAnimator valuesAnimator = ObjectAnimator.ofPropertyValuesHolder(this,
+                values.toArray(new PropertyValuesHolder[values.size()])).setDuration(
+                sAnimationSlideRightDuration);
+        valuesAnimator.setInterpolator(sRightInterpolator);
+        valuesAnimator.addListener(mConversationListListener);
+
+        mOutstandingAnimator = valuesAnimator;
+        AnimatorSet transitionSet = new AnimatorSet();
+        transitionSet.playTogether(animator, valuesAnimator);
+        transitionSet.start();
+    }
+
+    private void enterConversationMode() {
+        mConversationView.setVisibility(View.VISIBLE);
+
+        // On the initial call, measurements may not have been done (i.e. this Layout has never
+        // been rendered), so no animation will be done.
+        if (getMeasuredWidth() == 0) {
+            mListCollapsed = true;
+            onFinishEnteringConversationMode();
+            return;
+        }
+
+        // Prepare for animation.
+        mAnimatingFade = true;
+        captureListBitmaps();
+        ArrayList<PropertyValuesHolder> values = Lists.newArrayList();
+
+        // Slide labels out towards the left off screen.
+        int labelsWidth = mLabelsView.getMeasuredWidth();
+        values.add(PropertyValuesHolder.ofInt("labelsLeft", 0, -labelsWidth));
+
+        // Shrink the conversation list to make room for the conversation, and default
+        // it to collapsed in case it is collapsible.
+        mListCollapsed = true;
+        int targetWidth = computeConversationListWidth();
+        setConversationListWidth(targetWidth);
+
+        int currentListLeft = labelsWidth + getConversationListLeft();
+        int targetListLeft = computeConversationListLeft(targetWidth);
+        setConversationListLeft(targetListLeft);
+        if (currentListLeft != targetListLeft) {
+            values.add(
+                    PropertyValuesHolder.ofInt("listBitmapLeft", currentListLeft, targetListLeft));
+            values.add(PropertyValuesHolder.ofInt("listBitmapAlpha", 255, 0));
+            values.add(
+                    PropertyValuesHolder.ofInt("listLeft",
+                            currentListLeft + mListView.getWidth() - targetWidth, targetListLeft));
+            values.add(PropertyValuesHolder.ofInt("listAlpha", 0, 255));
+        }
+
+        // Set up the conversation view.
+        // Performance note: do not animate the width of this, as it is very
+        // expensive to reflow in the WebView.
+        setConversationWidth(computeConversationWidth());
+        values.add(PropertyValuesHolder.ofInt(
+                "conversationLeft", getWidth(), targetListLeft + targetWidth));
+
+        startLayoutAnimation(sAnimationSlideLeftDuration, mConversationListener, sLeftInterpolator,
+                values.toArray(new PropertyValuesHolder[values.size()]));
+    }
+
+    /**
+     * @return The left position of the conversation list relative to its uncollapsed position.
+     *     This is only relevant in a collapsible view, and will be 0 otherwise.
+     */
+    public int getConversationListLeft() {
+        return ((ViewGroup.MarginLayoutParams) mConversationListContainer.getLayoutParams())
+                .leftMargin;
+    }
+
+    /**
+     * Initializes the layout with a specific context.
+     */
+    @VisibleForTesting
+    public void initializeLayout(Context context) {
+        mContext = context;
+
+        Resources res = getResources();
+        mLabelsView = findViewById(R.id.labels_pane);
+        mConversationListContainer = findViewById(R.id.conversation_column_container);
+        mListView = findViewById(R.id.conversation_list);
+        mConversationView = findViewById(R.id.conversation_pane_container);
+        mConversationViewOverlay = findViewById(R.id.conversation_overlay);
+
+        mConversationViewOverlay.setOnTouchListener(this);
+
+        sAnimationSlideLeftDuration = res.getInteger(R.integer.activity_slide_left_duration);
+        sAnimationSlideRightDuration = res.getInteger(R.integer.activity_slide_right_duration);
+        sAnimationCollapseDuration = res.getInteger(R.integer.activity_collapse_duration);
+        final int sLabelListWeight = res.getInteger(R.integer.label_list_weight);
+        final int sConversationListWeight = res.getInteger(R.integer.conversation_list_weight);
+        final int sConversationViewWeight = res.getInteger(R.integer.conversation_view_weight);
+        sScaledLabelListWeight = (double) sLabelListWeight
+                / (sLabelListWeight + sConversationListWeight);
+        sScaledConversationListWeight = (double) sConversationListWeight
+                / (sConversationListWeight + sConversationViewWeight);
+
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setColor(android.R.color.white);
+        mListPaint = new Paint();
+        mListPaint.setAntiAlias(true);
+    }
+
+    private boolean isAnimatingFade() {
+        return mAnimatingFade;
+    }
+
+    /**
+     * @return whether the conversation list can be collapsed or not.
+     */
+    public boolean isConversationListCollapsible() {
+        return mContext.getResources().getInteger(R.integer.conversation_list_collapsible) != 0;
+    }
+
+    /**
+     * @return Whether or not the conversation list is visible on screen.
+     */
+    public boolean isConversationListVisible() {
+        return !isConversationListCollapsible() || !mListCollapsed;
+    }
+
+    /**
+     * Finalizes state after animations settle when collapsing the conversation list.
+     */
+    private void onCollapseList() {
+        mConversationViewOverlay.setVisibility(View.GONE);
+        dispatchConversationListVisibilityChange();
+    }
+
+    /**
+     * Finalizes state after animations settle when entering the conversation list mode.
+     */
+    private void onFinishEnteringConversationListMode() {
+        mListCollapsed = false;
+        mConversationView.setVisibility(View.GONE);
+        mConversationViewOverlay.setVisibility(View.GONE);
+
+        // Once animations settle, the conversation list always takes up the
+        // remaining space that is on the right, so avoid hard pixel values,
+        // since this avoids manual re-computations when the parent container
+        // size changes for any reason (e.g. orientation change).
+        mConversationListContainer.getLayoutParams().width =
+                ViewGroup.LayoutParams.MATCH_PARENT;
+
+        dispatchConversationListVisibilityChange();
+        dispatchConversationVisibilityChanged(false);
+    }
+
+    /**
+     * Finalizes state after animations settle when entering conversation mode.
+     */
+    private void onFinishEnteringConversationMode() {
+        mLabelsView.setVisibility(View.GONE);
+
+        setConversationListWidth(computeConversationListWidth());
+
+        if (isConversationListCollapsible()) {
+            onCollapseList();
+        }
+
+        dispatchConversationVisibilityChanged(true);
+    }
+
+    /**
+     * Handles a size change and sets layout parameters as necessary.
+     * Most of the time this occurs for an orientation change, but theoretically could occur
+     * if the Gmail layout was included in a larger ViewGroup.
+     *
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        if (w == oldw) {
+            // Only width changes are relevant to our logic.
+            return;
+        }
+
+        switch (currentMode) {
+            case ViewMode.CONVERSATION_LIST:
+                setLabelListWidth(computeLabelListWidth());
+                break;
+
+            case ViewMode.CONVERSATION:
+                final int conversationListWidth = computeConversationListWidth(w);
+                setConversationListWidth(conversationListWidth);
+                setConversationWidth(computeConversationWidth(w));
+                setConversationListLeft(computeConversationListLeft(conversationListWidth));
+                break;
+        }
+
+        // Request a measure pass here so all children views can be measured correctly before
+        // layout.
+        int widthSpec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY);
+        int heightSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
+        measure(widthSpec, heightSpec);
+    }
+
+    @Override
+    public boolean onTouch(View target, MotionEvent event) {
+        if (isConversationListCollapsible() && (target == mConversationViewOverlay)) {
+            collapseList();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Finalizes state after animations complete when expanding the conversation list.
+     */
+    private void onUncollapseList() {
+        if (isConversationListCollapsible()) {
+            mConversationViewOverlay.setVisibility(View.VISIBLE);
+        } else {
+            mConversationViewOverlay.setVisibility(View.GONE);
+        }
+        dispatchConversationListVisibilityChange();
+    }
+
+    @Override
+    public void onViewModeChanged(int newMode) {
+        currentMode = newMode;
+        // Finish the current animation before changing mode.
+        if (mOutstandingAnimator != null) {
+            mOutstandingAnimator.cancel();
+        }
+        switch (currentMode) {
+            case ViewMode.CONVERSATION:
+                enterConversationMode();
+                break;
+            case ViewMode.CONVERSATION_LIST:
+                enterConversationListMode();
+                break;
+        }
+    }
+
+    /**
+     * Sets the left position of the conversation fragment. Used by animators.
+     * Not to be used externally.
+     * @hide
+     */
+    private void setConversationLeft(int left) {
+        mConversationLeft = left;
+        invalidate();
+    }
+
+    /**
+     * Sets the relative left position of the conversation list.
+     */
+    private void setConversationListLeft(int left) {
+        ((ViewGroup.MarginLayoutParams) mConversationListContainer.getLayoutParams())
+            .leftMargin = left;
+        requestLayout();
+    }
+
+    /**
+     * Sets the width of the conversation list.
+     */
+    private void setConversationListWidth(int width) {
+        mConversationListContainer.getLayoutParams().width = width;
+        requestLayout();
+    }
+
+    /**
+     * Sets the width of the conversation pane.
+     */
+    private void setConversationWidth(int width) {
+        mConversationView.getLayoutParams().width = width;
+        requestLayout();
+    }
+
+    /**
+     * Sets the width of the label list pane.
+     * Used internally and by animators. Not to be used externally.
+     */
+    private void setLabelListWidth(int width) {
+        mLabelsView.getLayoutParams().width = width;
+        // Mindy points out that this is strange. Instead of requesting a layout for the labels
+        // view, we should be requesting a layout for the entire view.
+        // TODO(viki): Change to this.requestLayout() and see if there is any improvement or loss
+        mLabelsView.requestLayout();
+    }
+
+    // TODO(viki): I think most of the next methods aren't being used. Rather than removing them,
+    // I'm marking them private to remove once the application is complete.
+    /**
+     * Sets the left position of the labels fragment. Used by animators. Not to
+     * be used externally.
+     * @hide
+     */
+    private void setLabelsLeft(int left) {
+        mLabelsLeft = left;
+        invalidate();
+    }
+
+    /**
+     * Sets the alpha of the conversation list. Used by animators. Not to be used externally.
+     * @hide
+     */
+    private void setListAlpha(int alpha) {
+        mListAlpha = alpha;
+        invalidate();
+    }
+
+    /**
+     * Sets the alpha of the conversation list bitmap. Used by animators. Not to be used externally.
+     * @hide
+     */
+    private void setListBitmapAlpha(int alpha) {
+        mListPaint.setAlpha(alpha);
+        invalidate();
+    }
+
+    /**
+     * Sets the left position of the conversation list bitmap. Used by animators. Not to be used
+     * externally.
+     * @hide
+     */
+    private void setListBitmapLeft(int left) {
+        mListBitmapLeft = left;
+        invalidate();
+    }
+
+    /**
+     * Sets the {@link LayoutListener} for this object.
+     */
+    public void setListener(LayoutListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Sets the left position of the conversation list. Used by animators. Not to be used
+     * externally.
+     * @hide
+     */
+    private void setListLeft(int left) {
+        mListLeft = left;
+        invalidate();
+    }
+
+    /**
+     * Helper method to start animation.
+     */
+    private void startLayoutAnimation(
+            int duration, AnimatorListener listener, TimeInterpolator interpolator,
+            PropertyValuesHolder... values) {
+        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
+                this, values).setDuration(duration);
+        animator.setInterpolator(interpolator);
+        if (listener != null) {
+            animator.addListener(listener);
+        }
+
+        mOutstandingAnimator = animator;
+        animator.start();
+    }
+
+    /**
+     * Expands the conversation list out from the left if it is in a collapsed state.
+     * Only applies in portrait mode.
+     */
+    public boolean uncollapseList() {
+        if (!mListCollapsed) {
+            return false;
+        }
+        mListCollapsed = false;
+
+        PropertyValuesHolder listLeftValues = PropertyValuesHolder.ofInt(
+                "conversationListLeft",
+                getConversationListLeft(), 0);
+
+        startLayoutAnimation(sAnimationCollapseDuration, mUncollapseListListener,
+                sCollapseInterpolator, listLeftValues);
+        return true;
+    }
+}
diff --git a/src/com/android/mail/ui/ViewMode.java b/src/com/android/mail/ui/ViewMode.java
index 96c42bd..6357b47 100644
--- a/src/com/android/mail/ui/ViewMode.java
+++ b/src/com/android/mail/ui/ViewMode.java
@@ -77,7 +77,7 @@
     }
 
     /**
-     *  Adds a listener from this view mode.
+     * Adds a listener from this view mode.
      * Must happen in the UI thread.
      */
     public void addListener(ModeChangeListener listener) {