Reworking the ActionBar tab API

Change-Id: Ifbcdc61b4a24633cc1a329c73923b95b03e9ecf0
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 38086f0..29f2e30 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -123,72 +123,6 @@
     public abstract int getSelectedNavigationItem();
 
     /**
-     * Set the action bar into standard navigation mode, supplying a title and subtitle.
-     * 
-     * Standard navigation mode is default. The title is automatically set to the
-     * name of your Activity. Subtitles are displayed underneath the title, usually
-     * in a smaller font or otherwise less prominently than the title. Subtitles are
-     * good for extended descriptions of activity state.
-     *
-     * @param title The action bar's title. null is treated as an empty string.
-     * @param subtitle The action bar's subtitle. null will remove the subtitle entirely.
-     *
-     * @see #setStandardNavigationMode()
-     * @see #setStandardNavigationMode(CharSequence)
-     * @see #setStandardNavigationMode(int)
-     * @see #setStandardNavigationMode(int, int)
-     */
-    public abstract void setStandardNavigationMode(CharSequence title, CharSequence subtitle);
-
-    /**
-     * Set the action bar into standard navigation mode, supplying a title and subtitle.
-     * 
-     * Standard navigation mode is default. The title is automatically set to the
-     * name of your Activity. Subtitles are displayed underneath the title, usually
-     * in a smaller font or otherwise less prominently than the title. Subtitles are
-     * good for extended descriptions of activity state.
-     *
-     * @param titleResId Resource ID of a title string
-     * @param subtitleResId Resource ID of a subtitle string
-     *
-     * @see #setStandardNavigationMode()
-     * @see #setStandardNavigationMode(CharSequence)
-     * @see #setStandardNavigationMode(CharSequence, CharSequence)
-     * @see #setStandardNavigationMode(int)
-     */
-    public abstract void setStandardNavigationMode(int titleResId, int subtitleResId);
-
-    /**
-     * Set the action bar into standard navigation mode, supplying a title and subtitle.
-     * 
-     * Standard navigation mode is default. The title is automatically set to the
-     * name of your Activity on startup if an action bar is present.
-     *
-     * @param title The action bar's title. null is treated as an empty string.
-     *
-     * @see #setStandardNavigationMode()
-     * @see #setStandardNavigationMode(CharSequence, CharSequence)
-     * @see #setStandardNavigationMode(int)
-     * @see #setStandardNavigationMode(int, int)
-     */
-    public abstract void setStandardNavigationMode(CharSequence title);
-
-    /**
-     * Set the action bar into standard navigation mode, supplying a title and subtitle.
-     * 
-     * Standard navigation mode is default. The title is automatically set to the
-     * name of your Activity on startup if an action bar is present.
-     *
-     * @param titleResId Resource ID of a title string
-     *
-     * @see #setStandardNavigationMode()
-     * @see #setStandardNavigationMode(CharSequence)
-     * @see #setStandardNavigationMode(CharSequence, CharSequence)
-     * @see #setStandardNavigationMode(int, int)
-     */
-    public abstract void setStandardNavigationMode(int titleResId);
-
-    /**
      * Set the action bar into standard navigation mode, using the currently set title
      * and/or subtitle.
      *
@@ -324,18 +258,6 @@
     public abstract void setTabNavigationMode();
 
     /**
-     * Set the action bar into tabbed navigation mode.
-     *
-     * @param containerViewId Id of the container view where tab content fragments should appear.
-     *
-     * @see #addTab(Tab)
-     * @see #insertTab(Tab, int)
-     * @see #removeTab(Tab)
-     * @see #removeTabAt(int)
-     */
-    public abstract void setTabNavigationMode(int containerViewId);
-
-    /**
      * Create and return a new {@link Tab}.
      * This tab will not be included in the action bar until it is added.
      *
@@ -354,13 +276,13 @@
     public abstract void addTab(Tab tab);
 
     /**
-     * Insert a tab for use in tabbed navigation mode. The tab will be inserted at
+     * Add a tab for use in tabbed navigation mode. The tab will be inserted at
      * <code>position</code>.
      *
      * @param tab The tab to add
      * @param position The new position of the tab
      */
-    public abstract void insertTab(Tab tab, int position);
+    public abstract void addTab(Tab tab, int position);
 
     /**
      * Remove a tab from the action bar.
@@ -384,6 +306,14 @@
     public abstract void selectTab(Tab tab);
 
     /**
+     * Returns the currently selected tab if in tabbed navigation mode and there is at least
+     * one tab present.
+     *
+     * @return The currently selected tab or null
+     */
+    public abstract Tab getSelectedTab();
+
+    /**
      * Retrieve the current height of the ActionBar.
      *
      * @return The ActionBar's height
@@ -477,22 +407,68 @@
         public abstract void setText(CharSequence text);
 
         /**
-         * Returns the fragment that will be shown when this tab is selected.
+         * Set a custom view to be used for this tab. This overrides values set by
+         * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}.
          *
-         * @return Fragment associated with this tab
+         * @param view Custom view to be used as a tab.
          */
-        public abstract Fragment getFragment();
+        public abstract void setCustomView(View view);
 
         /**
-         * Set the fragment that will be shown when this tab is selected.
+         * Retrieve a previously set custom view for this tab.
          *
-         * @param fragment Fragment to associate with this tab
+         * @return The custom view set by {@link #setCustomView(View)}.
          */
-        public abstract void setFragment(Fragment fragment);
+        public abstract View getCustomView();
+
+        /**
+         * Give this Tab an arbitrary object to hold for later use.
+         *
+         * @param obj Object to store
+         */
+        public abstract void setTag(Object obj);
+
+        /**
+         * @return This Tab's tag object.
+         */
+        public abstract Object getTag();
+
+        /**
+         * Set the {@link TabListener} that will handle switching to and from this tab.
+         * All tabs must have a TabListener set before being added to the ActionBar.
+         *
+         * @param listener Listener to handle tab selection events
+         */
+        public abstract void setTabListener(TabListener listener);
 
         /**
          * Select this tab. Only valid if the tab has been added to the action bar.
          */
         public abstract void select();
     }
+
+    /**
+     * Callback interface invoked when a tab is focused, unfocused, added, or removed.
+     */
+    public interface TabListener {
+        /**
+         * Called when a tab enters the selected state.
+         *
+         * @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.
+         */
+        public void onTabSelected(Tab tab, FragmentTransaction ft);
+
+        /**
+         * Called when a tab exits the selected state.
+         *
+         * @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.
+         */
+        public void onTabUnselected(Tab tab, FragmentTransaction ft);
+    }
 }
diff --git a/core/java/android/app/BackStackEntry.java b/core/java/android/app/BackStackEntry.java
index 00c2fc4..71fd5e5 100644
--- a/core/java/android/app/BackStackEntry.java
+++ b/core/java/android/app/BackStackEntry.java
@@ -490,4 +490,8 @@
     public int getTransitionStyle() {
         return mTransitionStyle;
     }
+
+    public boolean isEmpty() {
+        return mNumOp == 0;
+    }
 }
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 04598a3..9d44106 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -85,6 +85,12 @@
      * @return Returns the same FragmentTransaction instance.
      */
     public FragmentTransaction show(Fragment fragment);
+
+    /**
+     * @return <code>true</code> if this transaction contains no operations,
+     *         <code>false</code> otherwise.
+     */
+    public boolean isEmpty();
     
     /**
      * Bit mask that is set for all enter transitions.
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 1048352..cf029d2 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -122,17 +122,6 @@
     }
 
     @Override
-    public void setStandardNavigationMode(int titleResId, int subtitleResId) {
-        setStandardNavigationMode(mContext.getString(titleResId),
-                mContext.getString(subtitleResId));
-    }
-
-    @Override
-    public void setStandardNavigationMode(int titleResId) {
-        setStandardNavigationMode(mContext.getString(titleResId));
-    }
-
-    @Override
     public void setTitle(int resId) {
         setTitle(mContext.getString(resId));
     }
@@ -169,19 +158,6 @@
         mActionView.setCallback(null);
     }
 
-    public void setStandardNavigationMode(CharSequence title) {
-        cleanupTabs();
-        setStandardNavigationMode(title, null);
-    }
-
-    public void setStandardNavigationMode(CharSequence title, CharSequence subtitle) {
-        cleanupTabs();
-        mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD);
-        mActionView.setTitle(title);
-        mActionView.setSubtitle(subtitle);
-        mActionView.setCallback(null);
-    }
-
     public void setSelectedNavigationItem(int position) {
         switch (mActionView.getNavigationMode()) {
         case NAVIGATION_MODE_TABS:
@@ -211,17 +187,7 @@
         if (mSelectedTab != null) {
             selectTab(null);
         }
-        if (!mTabs.isEmpty()) {
-            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
-                final FragmentTransaction trans = mActivity.openFragmentTransaction();
-                final int tabCount = mTabs.size();
-                for (int i = 0; i < tabCount; i++) {
-                    trans.remove(mTabs.get(i).getFragment());
-                }
-                trans.commit();
-            }
-            mTabs.clear();
-        }
+        mTabs.clear();
     }
 
     public void setTitle(CharSequence title) {
@@ -295,31 +261,23 @@
     private void configureTab(Tab tab, int position) {
         final TabImpl tabi = (TabImpl) tab;
         final boolean isFirstTab = mTabs.isEmpty();
-        final FragmentTransaction trans = mActivity.openFragmentTransaction();
-        final Fragment frag = tabi.getFragment();
+        final ActionBar.TabListener callback = tabi.getCallback();
+
+        if (callback == null) {
+            throw new IllegalStateException("Action Bar Tab must have a Callback");
+        }
 
         tabi.setPosition(position);
         mTabs.add(position, tabi);
 
-        if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
-            if (!frag.isAdded()) {
-                trans.add(mTabContainerViewId, frag);
-            }
-        }
-
         if (isFirstTab) {
-            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
-                trans.show(frag);
-            } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
-                trans.add(mTabContainerViewId, frag);
-            }
+            final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction();
             mSelectedTab = tabi;
-        } else {
-            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
-                trans.hide(frag);
+            callback.onTabSelected(tab, trans);
+            if (!trans.isEmpty()) {
+                trans.commit();
             }
         }
-        trans.commit();
     }
 
     @Override
@@ -329,8 +287,8 @@
     }
 
     @Override
-    public void insertTab(Tab tab, int position) {
-        mActionView.insertTab(tab, position);
+    public void addTab(Tab tab, int position) {
+        mActionView.addTab(tab, position);
         configureTab(tab, position);
     }
 
@@ -367,35 +325,29 @@
     }
 
     @Override
-    public void setTabNavigationMode(int containerViewId) {
-        mTabContainerViewId = containerViewId;
-        setTabNavigationMode();
-    }
-
-    @Override
     public void selectTab(Tab tab) {
         if (mSelectedTab == tab) {
             return;
         }
 
         mActionView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
-        final FragmentTransaction trans = mActivity.openFragmentTransaction();
+        final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction();
         if (mSelectedTab != null) {
-            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
-                trans.hide(mSelectedTab.getFragment());
-            } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
-                trans.remove(mSelectedTab.getFragment());
-            }
-        }
-        if (tab != null) {
-            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
-                trans.show(tab.getFragment());
-            } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
-                trans.add(mTabContainerViewId, tab.getFragment());
-            }
+            mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans);
         }
         mSelectedTab = (TabImpl) tab;
-        trans.commit();
+        if (mSelectedTab != null) {
+            mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans);
+        }
+
+        if (!trans.isEmpty()) {
+            trans.commit();
+        }
+    }
+
+    @Override
+    public Tab getSelectedTab() {
+        return mSelectedTab;
     }
 
     @Override
@@ -542,14 +494,40 @@
      * @hide
      */
     public class TabImpl extends ActionBar.Tab {
-        private Fragment mFragment;
+        private ActionBar.TabListener mCallback;
+        private Object mTag;
         private Drawable mIcon;
         private CharSequence mText;
         private int mPosition;
+        private View mCustomView;
 
         @Override
-        public Fragment getFragment() {
-            return mFragment;
+        public Object getTag() {
+            return mTag;
+        }
+
+        @Override
+        public void setTag(Object tag) {
+            mTag = tag;
+        }
+
+        public ActionBar.TabListener getCallback() {
+            return mCallback;
+        }
+
+        @Override
+        public void setTabListener(ActionBar.TabListener callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public View getCustomView() {
+            return mCustomView;
+        }
+
+        @Override
+        public void setCustomView(View view) {
+            mCustomView = view;
         }
 
         @Override
@@ -572,11 +550,6 @@
         }
 
         @Override
-        public void setFragment(Fragment fragment) {
-            mFragment = fragment;
-        }
-
-        @Override
         public void setIcon(Drawable icon) {
             mIcon = icon;
         }
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index b9e4e46..6b3d353 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -41,6 +41,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
+import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.Spinner;
@@ -87,6 +88,7 @@
     private TextView mTitleView;
     private TextView mSubtitleView;
     private Spinner mSpinner;
+    private HorizontalScrollView mTabScrollView;
     private LinearLayout mTabLayout;
     private View mCustomNavView;
     
@@ -119,6 +121,8 @@
 
     private OnClickListener mHomeClickListener = null;
 
+    private OnClickListener mTabClickListener = null;
+
     public ActionBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -346,8 +350,9 @@
                 break;
             case ActionBar.NAVIGATION_MODE_TABS:
                 if (mTabLayout != null) {
-                    removeView(mTabLayout);
+                    removeView(mTabScrollView);
                     mTabLayout = null;
+                    mTabScrollView = null;
                 }
             }
             
@@ -365,8 +370,10 @@
                 addView(mCustomNavView);
                 break;
             case ActionBar.NAVIGATION_MODE_TABS:
+                mTabScrollView = new HorizontalScrollView(getContext());
                 mTabLayout = new LinearLayout(getContext());
-                addView(mTabLayout);
+                mTabScrollView.addView(mTabLayout);
+                addView(mTabScrollView);
                 break;
             }
             mNavigationMode = mode;
@@ -401,20 +408,24 @@
     private TabView createTabView(ActionBar.Tab tab) {
         final TabView tabView = new TabView(getContext(), tab);
         tabView.setFocusable(true);
-        tabView.setOnClickListener(new TabClickListener());
+
+        if (mTabClickListener == null) {
+            mTabClickListener = new TabClickListener();
+        }
+        tabView.setOnClickListener(mTabClickListener);
         return tabView;
     }
 
     public void addTab(ActionBar.Tab tab) {
         final boolean isFirst = mTabLayout.getChildCount() == 0;
-        final TabView tabView = createTabView(tab);
+        View tabView = createTabView(tab);
         mTabLayout.addView(tabView);
         if (isFirst) {
             tabView.setSelected(true);
         }
     }
 
-    public void insertTab(ActionBar.Tab tab, int position) {
+    public void addTab(ActionBar.Tab tab, int position) {
         final boolean isFirst = mTabLayout.getChildCount() == 0;
         final TabView tabView = createTabView(tab);
         mTabLayout.addView(tabView, position);
@@ -595,8 +606,8 @@
             }
             break;
         case ActionBar.NAVIGATION_MODE_TABS:
-            if (mTabLayout != null) {
-                mTabLayout.measure(
+            if (mTabScrollView != null) {
+                mTabScrollView.measure(
                         MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
                         MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
             }
@@ -663,8 +674,8 @@
             }
             break;
         case ActionBar.NAVIGATION_MODE_TABS:
-            if (mTabLayout != null) {
-                x += positionChild(mTabLayout, x, y, contentHeight) + mSpacing;
+            if (mTabScrollView != null) {
+                x += positionChild(mTabScrollView, x, y, contentHeight) + mSpacing;
             }
         }
 
@@ -703,31 +714,36 @@
             super(context);
             mTab = tab;
 
-            // TODO Style tabs based on the theme
+            final View custom = tab.getCustomView();
+            if (custom != null) {
+                addView(custom);
+            } else {
+                // TODO Style tabs based on the theme
 
-            final Drawable icon = tab.getIcon();
-            final CharSequence text = tab.getText();
+                final Drawable icon = tab.getIcon();
+                final CharSequence text = tab.getText();
 
-            if (icon != null) {
-                ImageView iconView = new ImageView(context);
-                iconView.setImageDrawable(icon);
-                LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
-                        LayoutParams.WRAP_CONTENT);
-                lp.gravity = Gravity.CENTER_VERTICAL;
-                iconView.setLayoutParams(lp);
-                addView(iconView);
-            }
+                if (icon != null) {
+                    ImageView iconView = new ImageView(context);
+                    iconView.setImageDrawable(icon);
+                    LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+                            LayoutParams.WRAP_CONTENT);
+                    lp.gravity = Gravity.CENTER_VERTICAL;
+                    iconView.setLayoutParams(lp);
+                    addView(iconView);
+                }
 
-            if (text != null) {
-                TextView textView = new TextView(context);
-                textView.setText(text);
-                textView.setSingleLine();
-                textView.setEllipsize(TruncateAt.END);
-                LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
-                        LayoutParams.WRAP_CONTENT);
-                lp.gravity = Gravity.CENTER_VERTICAL;
-                textView.setLayoutParams(lp);
-                addView(textView);
+                if (text != null) {
+                    TextView textView = new TextView(context);
+                    textView.setText(text);
+                    textView.setSingleLine();
+                    textView.setEllipsize(TruncateAt.END);
+                    LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+                            LayoutParams.WRAP_CONTENT);
+                    lp.gravity = Gravity.CENTER_VERTICAL;
+                    textView.setLayoutParams(lp);
+                    addView(textView);
+                }
             }
 
             setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,