ActionBar tab mode using fragments; needs styles.

Change-Id: I8f931dae447e7b64de827d3c17766e5f8ad794e2
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 67133e0..d0b3ac4 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -206,9 +206,11 @@
      *
      * @return The current navigation mode.
      * 
+     * @see #setStandardNavigationMode()
      * @see #setStandardNavigationMode(CharSequence)
      * @see #setStandardNavigationMode(CharSequence, CharSequence)
      * @see #setDropdownNavigationMode(SpinnerAdapter)
+     * @see #setTabNavigationMode()
      * @see #setCustomNavigationMode(View)
      */
     public abstract int getNavigationMode();
@@ -217,10 +219,98 @@
      * @return The current set of display options. 
      */
     public abstract int getDisplayOptions();
-    
+
+    /**
+     * Start a context mode controlled by <code>callback</code>.
+     * The {@link ContextModeCallback} will receive lifecycle events for the duration
+     * of the context mode.
+     *
+     * @param callback Callback handler that will manage this context mode.
+     */
     public abstract void startContextMode(ContextModeCallback callback);
+
+    /**
+     * Finish the current context mode.
+     */
     public abstract void finishContextMode();
-    
+
+    /**
+     * Set the action bar into tabbed navigation mode.
+     *
+     * @see #addTab(Tab)
+     * @see #insertTab(Tab, int)
+     * @see #removeTab(Tab)
+     * @see #removeTabAt(int)
+     */
+    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.
+     *
+     * @return A new Tab
+     *
+     * @see #addTab(Tab)
+     * @see #insertTab(Tab, int)
+     */
+    public abstract Tab newTab();
+
+    /**
+     * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list.
+     *
+     * @param tab Tab to add
+     */
+    public abstract void addTab(Tab tab);
+
+    /**
+     * Insert 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);
+
+    /**
+     * Remove a tab from the action bar.
+     *
+     * @param tab The tab to remove
+     */
+    public abstract void removeTab(Tab tab);
+
+    /**
+     * Remove a tab from the action bar.
+     *
+     * @param position Position of the tab to remove
+     */
+    public abstract void removeTabAt(int position);
+
+    /**
+     * Select the specified tab. If it is not a child of this action bar it will be added.
+     *
+     * @param tab Tab to select
+     */
+    public abstract void selectTab(Tab tab);
+
+    /**
+     * Select the tab at <code>position</code>
+     *
+     * @param position Position of the tab to select
+     */
+    public abstract void selectTabAt(int position);
+
     /**
      * Represents a contextual mode of the Action Bar. Context modes can be used for
      * modal interactions with activity content and replace the normal Action Bar until finished.
@@ -350,4 +440,74 @@
          */
         public boolean onNavigationItemSelected(int itemPosition, long itemId);
     }
+
+    /**
+     * A tab in the action bar.
+     *
+     * <p>Tabs manage the hiding and showing of {@link Fragment}s.
+     */
+    public static abstract class Tab {
+        /**
+         * An invalid position for a tab.
+         *
+         * @see #getPosition()
+         */
+        public static final int INVALID_POSITION = -1;
+
+        /**
+         * Return the current position of this tab in the action bar.
+         *
+         * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in
+         *         the action bar.
+         */
+        public abstract int getPosition();
+
+        /**
+         * Return the icon associated with this tab.
+         *
+         * @return The tab's icon
+         */
+        public abstract Drawable getIcon();
+
+        /**
+         * Return the text of this tab.
+         *
+         * @return The tab's text
+         */
+        public abstract CharSequence getText();
+
+        /**
+         * Set the icon displayed on this tab.
+         *
+         * @param icon The drawable to use as an icon
+         */
+        public abstract void setIcon(Drawable icon);
+
+        /**
+         * Set the text displayed on this tab. Text may be truncated if there is not
+         * room to display the entire string.
+         *
+         * @param text The text to display
+         */
+        public abstract void setText(CharSequence text);
+
+        /**
+         * Returns the fragment that will be shown when this tab is selected.
+         *
+         * @return Fragment associated with this tab
+         */
+        public abstract Fragment getFragment();
+
+        /**
+         * Set the fragment that will be shown when this tab is selected.
+         *
+         * @param fragment Fragment to associate with this tab
+         */
+        public abstract void setFragment(Fragment fragment);
+
+        /**
+         * Select this tab. Only valid if the tab has been added to the action bar.
+         */
+        public abstract void select();
+    }
 }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 8825b8c..4166b89 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1676,11 +1676,11 @@
      */
     private void initActionBar() {
         Window window = getWindow();
-        if (!window.hasFeature(Window.FEATURE_ACTION_BAR)) {
+        if (!window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
             return;
         }
         
-        mActionBar = new ActionBarImpl(getWindow().getDecorView());
+        mActionBar = new ActionBarImpl(this);
     }
     
     /**
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 6ac68aa..6cf455c 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -22,6 +22,9 @@
 import com.android.internal.widget.ActionBarView;
 
 import android.app.ActionBar;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.view.Menu;
@@ -31,6 +34,8 @@
 import android.widget.SpinnerAdapter;
 import android.widget.ViewAnimator;
 
+import java.util.ArrayList;
+
 /**
  * ActionBarImpl is the ActionBar implementation used
  * by devices of all screen sizes. If it detects a compatible decor,
@@ -42,10 +47,21 @@
     private static final int NORMAL_VIEW = 0;
     private static final int CONTEXT_VIEW = 1;
     
+    private static final int TAB_SWITCH_SHOW_HIDE = 0;
+    private static final int TAB_SWITCH_ADD_REMOVE = 1;
+
+    private Activity mActivity;
+
     private ViewAnimator mAnimatorView;
     private ActionBarView mActionView;
     private ActionBarContextView mUpperContextView;
     private LinearLayout mLowerContextView;
+
+    private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
+
+    private int mTabContainerViewId = android.R.id.content;
+    private TabImpl mSelectedTab;
+    private int mTabSwitchMode = TAB_SWITCH_ADD_REMOVE;
     
     private ContextMode mContextMode;
     
@@ -64,7 +80,9 @@
         }
     };
 
-    public ActionBarImpl(View decor) {
+    public ActionBarImpl(Activity activity) {
+        final View decor = activity.getWindow().getDecorView();
+        mActivity = activity;
         mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
         mUpperContextView = (ActionBarContextView) decor.findViewById(
                 com.android.internal.R.id.action_context_bar);
@@ -83,32 +101,54 @@
     }
 
     public void setCustomNavigationMode(View view) {
+        cleanupTabs();
         mActionView.setCustomNavigationView(view);
         mActionView.setCallback(null);
     }
 
     public void setDropdownNavigationMode(SpinnerAdapter adapter, NavigationCallback callback) {
+        cleanupTabs();
         mActionView.setCallback(callback);
         mActionView.setNavigationMode(NAVIGATION_MODE_DROPDOWN_LIST);
         mActionView.setDropdownAdapter(adapter);
     }
 
     public void setStandardNavigationMode() {
+        cleanupTabs();
         mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD);
         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);
     }
 
+    private void cleanupTabs() {
+        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();
+        }
+    }
+
     public void setTitle(CharSequence title) {
         mActionView.setTitle(title);
     }
@@ -174,6 +214,113 @@
         }
     }
 
+    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();
+
+        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);
+            }
+            mSelectedTab = tabi;
+        } else {
+            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
+                trans.hide(frag);
+            }
+        }
+        trans.commit();
+    }
+
+    @Override
+    public void addTab(Tab tab) {
+        mActionView.addTab(tab);
+        configureTab(tab, mTabs.size());
+    }
+
+    @Override
+    public void insertTab(Tab tab, int position) {
+        mActionView.insertTab(tab, position);
+        configureTab(tab, position);
+    }
+
+    @Override
+    public Tab newTab() {
+        return new TabImpl();
+    }
+
+    @Override
+    public void removeTab(Tab tab) {
+        removeTabAt(tab.getPosition());
+    }
+
+    @Override
+    public void removeTabAt(int position) {
+        mActionView.removeTabAt(position);
+        mTabs.remove(position);
+
+        final int newTabCount = mTabs.size();
+        for (int i = position; i < newTabCount; i++) {
+            mTabs.get(i).setPosition(i);
+        }
+
+        selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
+    }
+
+    @Override
+    public void setTabNavigationMode() {
+        mActionView.setNavigationMode(NAVIGATION_MODE_TABS);
+    }
+
+    @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();
+        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 = (TabImpl) tab;
+        trans.commit();
+    }
+
+    @Override
+    public void selectTabAt(int position) {
+        selectTab(mTabs.get(position));
+    }
+
     /**
      * @hide 
      */
@@ -233,6 +380,60 @@
             if (!actionItem.invoke()) {
                 mCallback.onContextItemClicked(this, item);
             }
+       }
+    }
+
+    /**
+     * @hide
+     */
+    public class TabImpl extends ActionBar.Tab {
+        private Fragment mFragment;
+        private Drawable mIcon;
+        private CharSequence mText;
+        private int mPosition;
+
+        @Override
+        public Fragment getFragment() {
+            return mFragment;
+        }
+
+        @Override
+        public Drawable getIcon() {
+            return mIcon;
+        }
+
+        @Override
+        public int getPosition() {
+            return mPosition;
+        }
+
+        public void setPosition(int position) {
+            mPosition = position;
+        }
+
+        @Override
+        public CharSequence getText() {
+            return mText;
+        }
+
+        @Override
+        public void setFragment(Fragment fragment) {
+            mFragment = fragment;
+        }
+
+        @Override
+        public void setIcon(Drawable icon) {
+            mIcon = icon;
+        }
+
+        @Override
+        public void setText(CharSequence text) {
+            mText = text;
+        }
+
+        @Override
+        public void select() {
+            selectTab(this);
         }
     }
 }
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index e919f1b..48707b9 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -31,8 +31,10 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
+import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.View;
@@ -85,6 +87,7 @@
     private TextView mTitleView;
     private TextView mSubtitleView;
     private Spinner mSpinner;
+    private LinearLayout mTabLayout;
     private View mCustomNavView;
     
     private boolean mShowMenu;
@@ -128,7 +131,8 @@
 
         ApplicationInfo info = context.getApplicationInfo();
         PackageManager pm = context.getPackageManager();
-        mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode, ActionBar.NAVIGATION_MODE_STANDARD);
+        mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode,
+                ActionBar.NAVIGATION_MODE_STANDARD);
         mTitle = a.getText(R.styleable.ActionBar_title);
         mSubtitle = a.getText(R.styleable.ActionBar_subtitle);
         mDisplayOptions = a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT);
@@ -271,6 +275,11 @@
                     mCustomNavView = null;
                 }
                 break;
+            case ActionBar.NAVIGATION_MODE_TABS:
+                if (mTabLayout != null) {
+                    removeView(mTabLayout);
+                    mTabLayout = null;
+                }
             }
             
             switch (mode) {
@@ -286,6 +295,10 @@
             case ActionBar.NAVIGATION_MODE_CUSTOM:
                 addView(mCustomNavView);
                 break;
+            case ActionBar.NAVIGATION_MODE_TABS:
+                mTabLayout = new LinearLayout(getContext());
+                addView(mTabLayout);
+                break;
             }
             mNavigationMode = mode;
             requestLayout();
@@ -308,6 +321,35 @@
         return mDisplayOptions;
     }
 
+    private TabView createTabView(ActionBar.Tab tab) {
+        final TabView tabView = new TabView(getContext(), tab);
+        tabView.setFocusable(true);
+        tabView.setOnClickListener(new TabClickListener());
+        return tabView;
+    }
+
+    public void addTab(ActionBar.Tab tab) {
+        final boolean isFirst = mTabLayout.getChildCount() == 0;
+        final TabView tabView = createTabView(tab);
+        mTabLayout.addView(tabView);
+        if (isFirst) {
+            tabView.setSelected(true);
+        }
+    }
+
+    public void insertTab(ActionBar.Tab tab, int position) {
+        final boolean isFirst = mTabLayout.getChildCount() == 0;
+        final TabView tabView = createTabView(tab);
+        mTabLayout.addView(tabView, position);
+        if (isFirst) {
+            tabView.setSelected(true);
+        }
+    }
+
+    public void removeTabAt(int position) {
+        mTabLayout.removeViewAt(position);
+    }
+
     @Override
     protected LayoutParams generateDefaultLayoutParams() {
         // Used by custom nav views if they don't supply layout params. Everything else
@@ -356,7 +398,7 @@
             
         case ActionBar.NAVIGATION_MODE_TABS:
             throw new UnsupportedOperationException(
-                    "Tab navigation isn't supported yet!");
+                    "Inflating tab navigation isn't supported yet!");
             
         case ActionBar.NAVIGATION_MODE_CUSTOM:
             if (mCustomNavView != null) {
@@ -381,6 +423,14 @@
         addView(mTitleLayout);
     }
 
+    public void setTabSelected(int position) {
+        final int tabCount = mTabLayout.getChildCount();
+        for (int i = 0; i < tabCount; i++) {
+            final View child = mTabLayout.getChildAt(i);
+            child.setSelected(i == position);
+        }
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
@@ -442,6 +492,13 @@
                         MeasureSpec.makeMeasureSpec(customNavHeight, customNavHeightMode));
             }
             break;
+        case ActionBar.NAVIGATION_MODE_TABS:
+            if (mTabLayout != null) {
+                mTabLayout.measure(
+                        MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+            }
+            break;
         }
 
         setMeasuredDimension(contentWidth, mContentHeight);
@@ -486,6 +543,10 @@
                 x += positionChild(mCustomNavView, x, y, contentHeight) + mSpacing;
             }
             break;
+        case ActionBar.NAVIGATION_MODE_TABS:
+            if (mTabLayout != null) {
+                x += positionChild(mTabLayout, x, y, contentHeight) + mSpacing;
+            }
         }
 
         x = r - l - getPaddingRight();
@@ -515,4 +576,59 @@
 
         return childWidth;
     }
+
+    private static class TabView extends LinearLayout {
+        private ActionBar.Tab mTab;
+
+        public TabView(Context context, ActionBar.Tab tab) {
+            super(context);
+            mTab = tab;
+
+            // TODO Style tabs based on the theme
+
+            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 (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,
+                    LayoutParams.MATCH_PARENT, 1));
+        }
+
+        public ActionBar.Tab getTab() {
+            return mTab;
+        }
+    }
+
+    private class TabClickListener implements OnClickListener {
+        public void onClick(View view) {
+            TabView tabView = (TabView) view;
+            tabView.getTab().select();
+            final int tabCount = mTabLayout.getChildCount();
+            for (int i = 0; i < tabCount; i++) {
+                final View child = mTabLayout.getChildAt(i);
+                child.setSelected(child == view);
+            }
+        }
+    }
 }