Fix bug 3156280 - Fix several issues with tab navigation in action bars.

Add the ability to restrict a FragmentTransaction's ability to be
added to the back stack. (It doesn't make sense for tabs or other
scenarios to allow this.)

Change-Id: I8fa2edb5f35c365e2483010ad13eb9993f5e6570
diff --git a/api/current.xml b/api/current.xml
index f14df1d..0cf46eb 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -19525,6 +19525,17 @@
 <parameter name="index" type="int">
 </parameter>
 </method>
+<method name="getTabCount"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getTitle"
  return="java.lang.CharSequence"
  abstract="true"
@@ -28322,6 +28333,17 @@
  visibility="public"
 >
 </method>
+<method name="disallowAddToBackStack"
+ return="android.app.FragmentTransaction"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="hide"
  return="android.app.FragmentTransaction"
  abstract="true"
@@ -28335,6 +28357,17 @@
 <parameter name="fragment" type="android.app.Fragment">
 </parameter>
 </method>
+<method name="isAddToBackStackAllowed"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isEmpty"
  return="boolean"
  abstract="true"
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index e185624..a57b54a 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -478,6 +478,12 @@
     public abstract Tab getTabAt(int index);
 
     /**
+     * Returns the number of tabs currently registered with the action bar.
+     * @return Tab count
+     */
+    public abstract int getTabCount();
+
+    /**
      * Retrieve the current height of the ActionBar.
      *
      * @return The ActionBar's height
@@ -626,7 +632,8 @@
          * @param tab The tab that was selected
          * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
          *        during a tab switch. The previous tab's unselect and this tab's select will be
-         *        executed in a single transaction.
+         *        executed in a single transaction. This FragmentTransaction does not support
+         *        being added to the back stack.
          */
         public void onTabSelected(Tab tab, FragmentTransaction ft);
 
@@ -636,7 +643,8 @@
          * @param tab The tab that was unselected
          * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
          *        during a tab switch. This tab's unselect and the newly selected tab's select
-         *        will be executed in a single transaction.
+         *        will be executed in a single transaction. This FragmentTransaction does not
+         *        support being added to the back stack.
          */
         public void onTabUnselected(Tab tab, FragmentTransaction ft);
 
@@ -646,7 +654,8 @@
          *
          * @param tab The tab that was reselected.
          * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
-         *        once this method returns.
+         *        once this method returns. This FragmentTransaction does not support
+         *        being added to the back stack.
          */
         public void onTabReselected(Tab tab, FragmentTransaction ft);
     }
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index e6cc0f9..c75777d 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -186,6 +186,7 @@
     int mTransition;
     int mTransitionStyle;
     boolean mAddToBackStack;
+    boolean mAllowAddToBackStack = true;
     String mName;
     boolean mCommitted;
     int mIndex;
@@ -346,11 +347,28 @@
     }
 
     public FragmentTransaction addToBackStack(String name) {
+        if (!mAllowAddToBackStack) {
+            throw new IllegalStateException(
+                    "This FragmentTransaction is not allowed to be added to the back stack.");
+        }
         mAddToBackStack = true;
         mName = name;
         return this;
     }
 
+    public boolean isAddToBackStackAllowed() {
+        return mAllowAddToBackStack;
+    }
+
+    public FragmentTransaction disallowAddToBackStack() {
+        if (mAddToBackStack) {
+            throw new IllegalStateException(
+                    "This transaction is already being added to the back stack");
+        }
+        mAllowAddToBackStack = false;
+        return this;
+    }
+
     public FragmentTransaction setBreadCrumbTitle(int res) {
         mBreadCrumbTitleRes = res;
         mBreadCrumbTitleText = null;
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index b00476bb..19da763 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -144,6 +144,22 @@
     public FragmentTransaction addToBackStack(String name);
 
     /**
+     * Returns true if this FragmentTransaction is allowed to be added to the back
+     * stack. If this method would return false, {@link #addToBackStack(String)}
+     * will throw {@link IllegalStateException}.
+     *
+     * @return True if {@link #addToBackStack(String)} is permitted on this transaction.
+     */
+    public boolean isAddToBackStackAllowed();
+
+    /**
+     * Disallow calls to {@link #addToBackStack(String)}. Any future calls to
+     * addToBackStack will throw {@link IllegalStateException}. If addToBackStack
+     * has already been called, this method will throw IllegalStateException.
+     */
+    public FragmentTransaction disallowAddToBackStack();
+
+    /**
      * Set the full title to show as a bread crumb when this transaction
      * is on the back stack, as used by {@link FragmentBreadCrumbs}.
      *
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 1d612e2..7cf369f 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -65,12 +65,15 @@
     private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
 
     private TabImpl mSelectedTab;
+    private int mSavedTabPosition = INVALID_POSITION;
     
     private ActionMode mActionMode;
     
     private static final int CONTEXT_DISPLAY_NORMAL = 0;
     private static final int CONTEXT_DISPLAY_SPLIT = 1;
     
+    private static final int INVALID_POSITION = -1;
+
     private int mContextDisplayMode;
 
     private boolean mClosingContext;
@@ -183,6 +186,8 @@
             selectTab(null);
         }
         mTabs.clear();
+        mActionView.removeAllTabs();
+        mSavedTabPosition = INVALID_POSITION;
     }
 
     public void setTitle(CharSequence title) {
@@ -310,6 +315,8 @@
 
     @Override
     public void removeTabAt(int position) {
+        int selectedTabPosition = mSelectedTab != null
+                ? mSelectedTab.getPosition() : mSavedTabPosition;
         mActionView.removeTabAt(position);
         mTabs.remove(position);
 
@@ -318,7 +325,9 @@
             mTabs.get(i).setPosition(i);
         }
 
-        selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
+        if (selectedTabPosition == position) {
+            selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
+        }
     }
 
     @Override
@@ -333,7 +342,13 @@
 
     @Override
     public void selectTab(Tab tab) {
-        final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction();
+        if (getNavigationMode() != NAVIGATION_MODE_TABS) {
+            mSavedTabPosition = tab != null ? tab.getPosition() : INVALID_POSITION;
+            return;
+        }
+
+        final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction()
+                .disallowAddToBackStack();
 
         if (mSelectedTab == tab) {
             if (mSelectedTab != null) {
@@ -623,12 +638,52 @@
     }
 
     @Override
+    public int getTabCount() {
+        return mTabs.size();
+    }
+
+    @Override
     public void setNavigationMode(int mode) {
+        final int oldMode = mActionView.getNavigationMode();
+        switch (oldMode) {
+            case NAVIGATION_MODE_TABS:
+                mSavedTabPosition = getSelectedNavigationIndex();
+                selectTab(null);
+                break;
+        }
         mActionView.setNavigationMode(mode);
+        switch (mode) {
+            case NAVIGATION_MODE_TABS:
+                if (mSavedTabPosition != INVALID_POSITION) {
+                    setSelectedNavigationItem(mSavedTabPosition);
+                    mSavedTabPosition = INVALID_POSITION;
+                }
+                break;
+        }
     }
 
     @Override
     public Tab getTabAt(int index) {
         return mTabs.get(index);
     }
+
+    /**
+     * This fragment is added when we're keeping a back stack in a tab switch
+     * transaction. We use it to change the selected tab in the action bar view
+     * when we back out.
+     */
+    private class SwitchSelectedTabViewFragment extends Fragment {
+        private int mSelectedTabIndex;
+
+        public SwitchSelectedTabViewFragment(int oldSelectedTab) {
+            mSelectedTabIndex = oldSelectedTab;
+        }
+
+        @Override
+        public void onDetach() {
+            if (mSelectedTabIndex >= 0 && mSelectedTabIndex < getTabCount()) {
+                mActionView.setTabSelected(mSelectedTabIndex);
+            }
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 95d6dd3..8ead9c8 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -487,6 +487,12 @@
         }
     }
 
+    public void removeAllTabs() {
+        if (mTabLayout != null) {
+            mTabLayout.removeAllViews();
+        }
+    }
+
     @Override
     protected LayoutParams generateDefaultLayoutParams() {
         // Used by custom nav views if they don't supply layout params. Everything else