subject in actionbar

Mostly a port of Gmail1 code. New and noteworthy:
* when switching between ActionBar list nav vs. custom display,
  toggle ActionBar.DISPLAY_SHOW_CUSTOM rather than messing with
  custom view visibility
  * This exposes something of a framework bug where collapsing
    an action view will leave views for all inactive navigation
    modes GONE, which I work around for now (b/6664203).
* ActionBar views were being inflated with system default Holo
  theme due to application vs. ActionBar context (wrong theme on
  the application context). Fixed that and un-inverted some
  existing styles.
* SnippetTextView: save off the last measurespec rather than
  inventing one as before.
* bring back an existing excellent behavior: since
  PagerAdapter.setPrimaryItem reacts too slowly to nicely
  change the action bar subject when paging, listen for
  OnPageChangeListener.onPageSelected in addition.

Bug: 6384157
Change-Id: I45d995a472d4b3c71f1371dc7b993923423b7cf7
diff --git a/res/layout-sw600dp/account_switch_spinner_item.xml b/res/layout-sw600dp/account_switch_spinner_item.xml
index b0cbab7..38e7a27 100644
--- a/res/layout-sw600dp/account_switch_spinner_item.xml
+++ b/res/layout-sw600dp/account_switch_spinner_item.xml
@@ -34,14 +34,14 @@
         android:layout_alignParentLeft="true">
         <TextView
             android:id="@+id/account_spinner_folder"
-            style="@android:style/TextAppearance.Holo.Widget.ActionBar.Title.Inverse"
+            style="@style/AccountSpinnerAnchorTextPrimary"
             android:singleLine="true"
             android:ellipsize="end"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content" />
         <TextView
             android:id="@+id/account_spinner_account_name"
-            style="@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle.Inverse"
+            style="@style/AccountSpinnerAnchorTextSecondary"
             android:singleLine="true"
             android:ellipsize="end"
             android:layout_marginRight="4dp"
diff --git a/res/layout-sw600dp/actionbar_view.xml b/res/layout-sw600dp/actionbar_view.xml
deleted file mode 100644
index db197aa..0000000
--- a/res/layout-sw600dp/actionbar_view.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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.ActionBarView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:email="http://schemas.android.com/apk/res/com.android.mail"
-    android:orientation="horizontal"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <include layout="@layout/actionbar_label" />
-
-    <include layout="@layout/actionbar_subject" />
-
-</com.android.mail.ui.ActionBarView>
diff --git a/res/layout/account_switch_spinner_item.xml b/res/layout/account_switch_spinner_item.xml
index f31a645..2ffa2e5 100644
--- a/res/layout/account_switch_spinner_item.xml
+++ b/res/layout/account_switch_spinner_item.xml
@@ -39,7 +39,7 @@
         android:background="@drawable/spinner_ab_holo_light">
             <TextView
                 android:id="@+id/account_spinner_folder"
-                style="@android:style/TextAppearance.Holo.Widget.ActionBar.Title.Inverse"
+                style="@style/AccountSpinnerAnchorTextPrimary"
                 android:singleLine="true"
                 android:ellipsize="end"
                 android:includeFontPadding="false"
@@ -47,8 +47,7 @@
                 android:layout_height="wrap_content" />
             <TextView
                 android:id="@+id/account_spinner_account_name"
-                style="@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle.Inverse"
-                android:textColor="@color/actionbar_secondary"
+                style="@style/AccountSpinnerAnchorTextSecondary"
                 android:singleLine="true"
                 android:ellipsize="end"
                 android:includeFontPadding="false"
diff --git a/res/layout/actionbar_subject.xml b/res/layout/actionbar_subject.xml
index 652b00f..1aa830c 100644
--- a/res/layout/actionbar_subject.xml
+++ b/res/layout/actionbar_subject.xml
@@ -26,5 +26,4 @@
     android:textSize="16dp"
     android:gravity="center_vertical"
     android:maxLines="2"
-    android:ellipsize="end"
-    android:visibility="gone" />
+    android:ellipsize="end" />
diff --git a/res/layout-sw600dp/actionbar_subject.xml b/res/values-sw600dp-land/constants.xml
similarity index 70%
rename from res/layout-sw600dp/actionbar_subject.xml
rename to res/values-sw600dp-land/constants.xml
index bc67976..a1c207c 100644
--- a/res/layout-sw600dp/actionbar_subject.xml
+++ b/res/values-sw600dp-land/constants.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
      Copyright (C) 2011 Google Inc.
      Licensed to The Android Open Source Project.
@@ -15,9 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<!-- conversation subject does not appear in action bar on tablets. -->
-<View xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="0dp"
-    android:layout_height="0dp"
-    android:visibility="gone" />
+<resources>
+    <!-- Whether to show conversation subject in conversation view -->
+    <bool name="show_conversation_subject">false</bool>
+</resources>
diff --git a/res/values-sw600dp-port/constants.xml b/res/values-sw600dp-port/constants.xml
index 44c537d..068c4d4 100644
--- a/res/values-sw600dp-port/constants.xml
+++ b/res/values-sw600dp-port/constants.xml
@@ -27,7 +27,4 @@
 
     <!-- Whether to show single or 2 pane search results -->
     <bool name="show_two_pane_search_results">false</bool>
-
-    <!-- Whether to show conversation subject in conversation view -->
-    <bool name="show_conversation_subject">true</bool>
 </resources>
\ No newline at end of file
diff --git a/res/values-sw600dp/constants.xml b/res/values-sw600dp/constants.xml
index e88dabf..4efbc24 100644
--- a/res/values-sw600dp/constants.xml
+++ b/res/values-sw600dp/constants.xml
@@ -29,7 +29,4 @@
 
     <!-- Whether to show single or 2 pane search results -->
     <bool name="show_two_pane_search_results">true</bool>
-
-    <!-- Whether to show conversation subject in conversation view -->
-    <bool name="show_conversation_subject">false</bool>
 </resources>
diff --git a/res/values-sw600dp/styles.xml b/res/values-sw600dp/styles.xml
index 2988c68..2060c9d 100644
--- a/res/values-sw600dp/styles.xml
+++ b/res/values-sw600dp/styles.xml
@@ -58,4 +58,7 @@
         <item name="android:textSize">30sp</item>
     </style>
 
+    <style name="AccountSpinnerAnchorTextPrimary" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
+    </style>
+
 </resources>
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 94eb701..a34b897 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -164,6 +164,9 @@
         <item name="android:textColor">@color/actionbar_secondary</item>
     </style>
 
+    <style name="AccountSpinnerAnchorTextPrimary" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
+    </style>
+
     <style name="AccountSpinnerAnchorTextSecondary" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle">
         <item name="android:textColor">@color/actionbar_secondary</item>
     </style>
diff --git a/src/com/android/mail/browse/ConversationPagerController.java b/src/com/android/mail/browse/ConversationPagerController.java
index 9e3abdf..4b70921 100644
--- a/src/com/android/mail/browse/ConversationPagerController.java
+++ b/src/com/android/mail/browse/ConversationPagerController.java
@@ -17,8 +17,10 @@
 
 package com.android.mail.browse;
 
+import android.app.Fragment;
 import android.app.FragmentManager;
 import android.support.v4.view.ViewPager;
+import android.support.v4.view.ViewPager.OnPageChangeListener;
 import android.view.View;
 
 import com.android.mail.R;
@@ -26,8 +28,10 @@
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.ui.AbstractActivityController;
+import com.android.mail.ui.ActivityController;
 import com.android.mail.ui.ConversationListCallbacks;
 import com.android.mail.ui.RestrictedActivity;
+import com.android.mail.ui.SubjectDisplayChanger;
 import com.android.mail.utils.LogUtils;
 
 /**
@@ -48,12 +52,13 @@
  * lifetime.
  *
  */
-public class ConversationPagerController {
+public class ConversationPagerController implements OnPageChangeListener {
 
     private ViewPager mPager;
     private ConversationPagerAdapter mPagerAdapter;
     private FragmentManager mFragmentManager;
     private ConversationListCallbacks mListController;
+    private SubjectDisplayChanger mSubjectDisplayChanger;
     private boolean mShown;
 
     private static final String LOG_TAG = new LogUtils().getLogTag();
@@ -73,10 +78,11 @@
     private static final boolean ENABLE_SINGLETON_INITIAL_LOAD = false;
 
     public ConversationPagerController(RestrictedActivity activity,
-            ConversationListCallbacks listController) {
+            ActivityController controller) {
         mFragmentManager = activity.getFragmentManager();
         mPager = (ViewPager) activity.findViewById(R.id.conversation_pane);
-        mListController = listController;
+        mListController = controller;
+        mSubjectDisplayChanger = controller.getSubjectDisplayChanger();
     }
 
     public void show(Account account, Folder folder, Conversation initialConversation) {
@@ -97,6 +103,8 @@
         mPagerAdapter.setPager(mPager);
         LogUtils.d(LOG_TAG, "IN CPC.show, adapter=%s", mPagerAdapter);
 
+        mPager.setOnPageChangeListener(this);
+
         LogUtils.d(LOG_TAG, "init pager adapter, count=%d initial=%s", mPagerAdapter.getCount(),
                 initialConversation.subject);
         mPager.setAdapter(mPagerAdapter);
@@ -119,8 +127,11 @@
         mShown = false;
         mPager.setVisibility(View.GONE);
 
+        mSubjectDisplayChanger.clearSubject();
+
         LogUtils.d(LOG_TAG, "IN CPC.hide, clearing adapter and unregistering list observer");
         mPager.setAdapter(null);
+        mPager.setOnPageChangeListener(null);
         cleanup();
     }
 
@@ -147,4 +158,22 @@
             mPagerAdapter.setSingletonMode(false);
         }
     }
+
+    @Override
+    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+        // no-op
+    }
+
+    @Override
+    public void onPageSelected(int position) {
+        final Fragment f = mPagerAdapter.getFragmentAt(position);
+        if (f != null) {
+            mPagerAdapter.setItemVisible(f, true);
+        }
+    }
+
+    @Override
+    public void onPageScrollStateChanged(int state) {
+        // no-op
+    }
 }
diff --git a/src/com/android/mail/browse/ConversationViewHeader.java b/src/com/android/mail/browse/ConversationViewHeader.java
index 1fdf732..188e9bc 100644
--- a/src/com/android/mail/browse/ConversationViewHeader.java
+++ b/src/com/android/mail/browse/ConversationViewHeader.java
@@ -27,7 +27,6 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.ViewGroup;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
@@ -36,7 +35,6 @@
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.ui.FolderDisplayer;
-import com.android.mail.utils.Utils;
 
 /**
  * A view for the subject and folders in the conversation view. This container
diff --git a/src/com/android/mail/browse/SnippetTextView.java b/src/com/android/mail/browse/SnippetTextView.java
index 911de40..29a354d 100644
--- a/src/com/android/mail/browse/SnippetTextView.java
+++ b/src/com/android/mail/browse/SnippetTextView.java
@@ -31,6 +31,8 @@
 public class SnippetTextView extends TextView {
 
     private int mMaxLines;
+    private int mLastWSpec;
+    private int mLastHSpec;
 
     public SnippetTextView(Context context) {
         this(context, null);
@@ -46,7 +48,15 @@
         mMaxLines = maxlines;
     }
 
-    public String getTextRemainder(final String text, int wSpec, int hSpec) {
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        mLastWSpec = widthMeasureSpec;
+        mLastHSpec = heightMeasureSpec;
+    }
+
+    public String getTextRemainder(final String text) {
         if (text == null || text.length() == 0) {
             return null;
         }
@@ -60,7 +70,7 @@
         Layout layout = getLayout();
 
         if (layout == null) {
-            measure(wSpec, hSpec);
+            measure(mLastWSpec, mLastHSpec);
             layout = getLayout();
         }
 
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index 0f3f4b7..9937923 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -222,11 +222,6 @@
     }
 
     @Override
-    public void clearSubject() {
-        // TODO(viki): Auto-generated method stub
-    }
-
-    @Override
     public Account getCurrentAccount() {
         return mAccount;
     }
@@ -242,12 +237,6 @@
     }
 
     @Override
-    public String getUnshownSubject(String subject) {
-        // Calculate how much of the subject is shown, and return the remaining.
-        return null;
-    }
-
-    @Override
     public final ConversationCursor getConversationListCursor() {
         return mConversationListCursor;
     }
@@ -309,14 +298,17 @@
      */
     private void initializeActionBar() {
         final ActionBar actionBar = mActivity.getActionBar();
-        mActionBarView = (ActionBarView) LayoutInflater.from(mContext).inflate(
-                R.layout.actionbar_view, null);
-        if (actionBar != null && mActionBarView != null) {
-            // Why have a different variable for the same thing? We should apply
-            // the same actions
-            // on mActionBarView instead.
-            mActionBarView.initialize(mActivity, this, mViewMode, actionBar, mRecentFolderList);
+        if (actionBar == null) {
+            return;
         }
+
+        // be sure to inherit from the ActionBar theme when inflating
+        final LayoutInflater inflater = LayoutInflater.from(actionBar.getThemedContext());
+        mActionBarView = (ActionBarView) inflater.inflate(R.layout.actionbar_view, null);
+        // Why have a different variable for the same thing? We should apply
+        // the same actions
+        // on mActionBarView instead.
+        mActionBarView.initialize(mActivity, this, mViewMode, actionBar, mRecentFolderList);
     }
 
     /**
@@ -1036,9 +1028,8 @@
     }
 
     @Override
-    public void setSubject(String subject) {
-        // Do something useful with the subject. This requires changing the
-        // conversation view's subject text.
+    public SubjectDisplayChanger getSubjectDisplayChanger() {
+        return mActionBarView;
     }
 
     /**
diff --git a/src/com/android/mail/ui/ActionBarView.java b/src/com/android/mail/ui/ActionBarView.java
index 5926ef7..c8681cb 100644
--- a/src/com/android/mail/ui/ActionBarView.java
+++ b/src/com/android/mail/ui/ActionBarView.java
@@ -18,9 +18,9 @@
 package com.android.mail.ui;
 
 import android.app.ActionBar;
+import android.app.ActionBar.OnNavigationListener;
 import android.app.SearchManager;
 import android.app.SearchableInfo;
-import android.app.ActionBar.OnNavigationListener;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
@@ -35,18 +35,18 @@
 import android.widget.SearchView;
 import android.widget.SearchView.OnQueryTextListener;
 import android.widget.SearchView.OnSuggestionListener;
-import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.mail.R;
 import com.android.mail.AccountSpinnerAdapter;
 import com.android.mail.ConversationListContext;
+import com.android.mail.R;
+import com.android.mail.browse.SnippetTextView;
 import com.android.mail.providers.Account;
+import com.android.mail.providers.Folder;
 import com.android.mail.providers.Settings;
 import com.android.mail.providers.UIProvider.AccountCapabilities;
 import com.android.mail.providers.UIProvider.FolderCapabilities;
 import com.android.mail.providers.UIProvider.LastSyncResult;
-import com.android.mail.providers.Folder;
 import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
 
@@ -57,7 +57,7 @@
  */
 public final class ActionBarView extends LinearLayout implements OnNavigationListener,
         ViewMode.ModeChangeListener, OnQueryTextListener, OnSuggestionListener,
-        MenuItem.OnActionExpandListener {
+        MenuItem.OnActionExpandListener, SubjectDisplayChanger {
     private ActionBar mActionBar;
     private RestrictedActivity mActivity;
     private ActivityController mController;
@@ -78,7 +78,7 @@
      */
     private Folder mFolder;
 
-    private TextView mSubjectView;
+    private SnippetTextView mSubjectView;
     private SearchView mSearchWidget;
     private MenuItem mHelpItem;
     private MenuItem mSendFeedbackItem;
@@ -101,7 +101,7 @@
      * method talks about why this is required.
      */
     private boolean mIgnoreFirstNavigation = true;
-    private Boolean mShowConversationSubject;
+    private final boolean mShowConversationSubject;
 
     public ActionBarView(Context context) {
         this(context, null);
@@ -113,6 +113,15 @@
 
     public ActionBarView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+
+        mShowConversationSubject = getResources().getBoolean(R.bool.show_conversation_subject);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mSubjectView = (SnippetTextView) findViewById(R.id.conversation_subject);
     }
 
     /**
@@ -348,10 +357,7 @@
         // We start out with every option enabled. Based on the current view, we disable actions
         // that are possible.
         LogUtils.d(LOG_TAG, "ActionBarView.onPrepareOptionsMenu().");
-        if (mSubjectView != null){
-            mSubjectView.setVisibility(GONE);
-        }
-        if (mFolderView != null){
+        if (mFolderView != null) {
             mFolderView.setVisibility(GONE);
         }
 
@@ -396,10 +402,7 @@
                 break;
             case ViewMode.CONVERSATION:
                 mActionBar.setDisplayHomeAsUpEnabled(true);
-                // FIXME: use a resource to have fine-grained control over whether the spinner
-                // or the subject appears
-                if (Utils.useTabletUI(mActivity.getActivityContext())
-                        && !showConversationSubject()) {
+                if (!mShowConversationSubject) {
                     showNavList();
                 } else {
                     setStandardMode();
@@ -418,8 +421,7 @@
             case ViewMode.SEARCH_RESULTS_CONVERSATION:
                 mActionBar.setDisplayHomeAsUpEnabled(true);
                 setStandardMode();
-                if (Utils.useTabletUI(mActivity.getActivityContext())
-                        && !showConversationSubject()) {
+                if (!mShowConversationSubject) {
                     setPopulatedSearchView();
                 }
                 break;
@@ -434,20 +436,12 @@
         return false;
     }
 
-    private boolean showConversationSubject() {
-        if (mShowConversationSubject == null) {
-            mShowConversationSubject = new Boolean(mActivity.getActivityContext().getResources()
-                    .getBoolean(R.bool.show_conversation_subject));
-        }
-        return mShowConversationSubject;
-    }
-
     /**
      * Put the ActionBar in List navigation mode. This starts the spinner up if it is missing.
      */
     private void showNavList() {
         mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
-        mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
+        mActionBar.setDisplayOptions(0,
                 ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
     }
 
@@ -456,6 +450,7 @@
      */
     private void setStandardMode() {
         mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+        mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, ActionBar.DISPLAY_SHOW_CUSTOM);
     }
 
     private void setPopulatedSearchView() {
@@ -583,6 +578,10 @@
 
     @Override
     public boolean onMenuItemActionCollapse(MenuItem item) {
+        // Work around b/6664203 by manually forcing this view to be VISIBLE upon ActionView
+        // collapse. DISPLAY_SHOW_CUSTOM will still control its final visibility.
+        setVisibility(VISIBLE);
+
         // When the action menu is collapsed, we have performed a search, pop the search fragment.
         mController.exitSearchMode();
         // Have to return true here. Unlike other callbacks, the return value here is whether
@@ -597,4 +596,31 @@
     public void requestRecentFoldersAndRedraw() {
         mSpinner.requestRecentFoldersAndRedraw();
     }
+
+    @Override
+    public void setSubject(String subject) {
+        if (!mShowConversationSubject) {
+            return;
+        }
+
+        mSubjectView.setText(subject);
+    }
+
+    @Override
+    public void clearSubject() {
+        if (!mShowConversationSubject) {
+            return;
+        }
+
+        mSubjectView.setText(null);
+    }
+
+    @Override
+    public String getUnshownSubject(String subject) {
+        if (!mShowConversationSubject) {
+            return subject;
+        }
+
+        return mSubjectView.getTextRemainder(subject);
+    }
 }
diff --git a/src/com/android/mail/ui/ActivityController.java b/src/com/android/mail/ui/ActivityController.java
index ede3a5e..d98130a 100644
--- a/src/com/android/mail/ui/ActivityController.java
+++ b/src/com/android/mail/ui/ActivityController.java
@@ -42,7 +42,7 @@
  * ActivityControllers are delegates that implement methods by calling underlying views to modify,
  * or respond to user action.
  */
-public interface ActivityController extends LayoutListener, SubjectDisplayChanger,
+public interface ActivityController extends LayoutListener,
         ModeChangeListener, ConversationListCallbacks,
         FolderChangeListener, AccountChangeListener, LoaderManager.LoaderCallbacks<Cursor>,
         ConversationSetObserver, ConversationListener, OnScrollListener,
@@ -295,4 +295,9 @@
      * Return the folder currently being viewed by the activity.
      */
     public Folder getFolder();
+
+    /**
+     * @return a {@link SubjectDisplayChanger}, or null
+     */
+    SubjectDisplayChanger getSubjectDisplayChanger();
 }
diff --git a/src/com/android/mail/ui/ControllableActivity.java b/src/com/android/mail/ui/ControllableActivity.java
index 689627b..665c60c 100644
--- a/src/com/android/mail/ui/ControllableActivity.java
+++ b/src/com/android/mail/ui/ControllableActivity.java
@@ -20,7 +20,6 @@
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.ui.ViewMode.ModeChangeListener;
-import com.android.mail.ui.FolderListFragment;
 
 /**
  * A controllable activity is an Activity that has a Controller attached. This activity must be
@@ -95,4 +94,6 @@
      * @return
      */
     ConversationUpdater getConversationUpdater();
+
+    SubjectDisplayChanger getSubjectDisplayChanger();
 }
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index b3436f1..18625dd 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -599,17 +599,26 @@
     }
 
     private void onConversationSeen() {
+        // Ignore unsafe calls made after a fragment is detached from an activity
+        final ControllableActivity activity = (ControllableActivity) getActivity();
+        if (activity == null) {
+            LogUtils.w(LOG_TAG, "ignoring onConversationSeen for conv=%s", mConversation.id);
+            return;
+        }
+
         // mark as read upon open
         if (!mConversation.read) {
-            mActivity.getListHandler().sendConversationRead(
+            activity.getListHandler().sendConversationRead(
                     AbstractActivityController.TAG_CONVERSATION_LIST, mConversation, true,
                     false /*local*/);
             mConversation.read = true;
         }
 
-        ControllableActivity activity = (ControllableActivity) getActivity();
-        if (activity != null) {
-            activity.onConversationSeen(mConversation);
+        activity.onConversationSeen(mConversation);
+
+        final SubjectDisplayChanger sdc = activity.getSubjectDisplayChanger();
+        if (sdc != null) {
+            sdc.setSubject(mConversation.subject);
         }
     }
 
@@ -631,8 +640,11 @@
 
     @Override
     public String getSubjectRemainder(String subject) {
-        // TODO: hook this up to action bar
-        return subject;
+        final SubjectDisplayChanger sdc = mActivity.getSubjectDisplayChanger();
+        if (sdc == null) {
+            return subject;
+        }
+        return sdc.getUnshownSubject(subject);
     }
     // END conversation header callbacks
 
diff --git a/src/com/android/mail/ui/FolderSelectionActivity.java b/src/com/android/mail/ui/FolderSelectionActivity.java
index c09dbfd..c8f9e89 100644
--- a/src/com/android/mail/ui/FolderSelectionActivity.java
+++ b/src/com/android/mail/ui/FolderSelectionActivity.java
@@ -314,4 +314,10 @@
     public ConversationUpdater getConversationUpdater() {
         return null;
     }
+
+    @Override
+    public SubjectDisplayChanger getSubjectDisplayChanger() {
+        return null;
+    }
+
 }
diff --git a/src/com/android/mail/ui/MailActivity.java b/src/com/android/mail/ui/MailActivity.java
index af26817..99cde2e 100644
--- a/src/com/android/mail/ui/MailActivity.java
+++ b/src/com/android/mail/ui/MailActivity.java
@@ -305,4 +305,9 @@
     public ConversationUpdater getConversationUpdater() {
         return mController;
     }
+
+    @Override
+    public SubjectDisplayChanger getSubjectDisplayChanger() {
+        return mController.getSubjectDisplayChanger();
+    }
 }
diff --git a/src/com/android/mail/utils/FragmentStatePagerAdapter2.java b/src/com/android/mail/utils/FragmentStatePagerAdapter2.java
index ff747c0..41e357c 100644
--- a/src/com/android/mail/utils/FragmentStatePagerAdapter2.java
+++ b/src/com/android/mail/utils/FragmentStatePagerAdapter2.java
@@ -40,6 +40,7 @@
  * <li>override-able {@link #setItemVisible(Fragment, boolean)} method for subclasses to
  * add supplemental handling of visibility hints manually on pre-v15 devices</li>
  * <li>add support to handle data set changes that cause item positions to change</li>
+ * <li>allow read access to existing Fragments by index ({@link #getFragmentAt(int)})</li>
  * </ul>
  */
 public abstract class FragmentStatePagerAdapter2 extends PagerAdapter {
@@ -230,4 +231,8 @@
             }
         }
     }
+
+    public Fragment getFragmentAt(int position) {
+        return mFragments.get(position);
+    }
 }