Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.app.LocalActivityManager;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.res.TypedArray;
     25 import android.graphics.drawable.Drawable;
     26 import android.os.Build;
     27 import android.text.TextUtils;
     28 import android.util.AttributeSet;
     29 import android.view.KeyEvent;
     30 import android.view.LayoutInflater;
     31 import android.view.SoundEffectConstants;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.view.ViewTreeObserver;
     35 import android.view.Window;
     36 
     37 import com.android.internal.R;
     38 
     39 import java.util.ArrayList;
     40 import java.util.List;
     41 
     42 /**
     43  * Container for a tabbed window view. This object holds two children: a set of tab labels that the
     44  * user clicks to select a specific tab, and a FrameLayout object that displays the contents of that
     45  * page. The individual elements are typically controlled using this container object, rather than
     46  * setting values on the child elements themselves.
     47  *
     48  */
     49 public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
     50 
     51     private static final int TABWIDGET_LOCATION_LEFT = 0;
     52     private static final int TABWIDGET_LOCATION_TOP = 1;
     53     private static final int TABWIDGET_LOCATION_RIGHT = 2;
     54     private static final int TABWIDGET_LOCATION_BOTTOM = 3;
     55     private TabWidget mTabWidget;
     56     private FrameLayout mTabContent;
     57     private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);
     58     /**
     59      * This field should be made private, so it is hidden from the SDK.
     60      * {@hide}
     61      */
     62     protected int mCurrentTab = -1;
     63     private View mCurrentView = null;
     64     /**
     65      * This field should be made private, so it is hidden from the SDK.
     66      * {@hide}
     67      */
     68     protected LocalActivityManager mLocalActivityManager = null;
     69     private OnTabChangeListener mOnTabChangeListener;
     70     private OnKeyListener mTabKeyListener;
     71 
     72     private int mTabLayoutId;
     73 
     74     public TabHost(Context context) {
     75         super(context);
     76         initTabHost();
     77     }
     78 
     79     public TabHost(Context context, AttributeSet attrs) {
     80         this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
     81     }
     82 
     83     public TabHost(Context context, AttributeSet attrs, int defStyleAttr) {
     84         this(context, attrs, defStyleAttr, 0);
     85     }
     86 
     87     public TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
     88         super(context, attrs);
     89 
     90         final TypedArray a = context.obtainStyledAttributes(
     91                 attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes);
     92 
     93         mTabLayoutId = a.getResourceId(R.styleable.TabWidget_tabLayout, 0);
     94         a.recycle();
     95 
     96         if (mTabLayoutId == 0) {
     97             // In case the tabWidgetStyle does not inherit from Widget.TabWidget and tabLayout is
     98             // not defined.
     99             mTabLayoutId = R.layout.tab_indicator_holo;
    100         }
    101 
    102         initTabHost();
    103     }
    104 
    105     private void initTabHost() {
    106         setFocusableInTouchMode(true);
    107         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
    108 
    109         mCurrentTab = -1;
    110         mCurrentView = null;
    111     }
    112 
    113     /**
    114      * Creates a new {@link TabSpec} associated with this tab host.
    115      *
    116      * @param tag tag for the tab specification, must be non-null
    117      * @throws IllegalArgumentException If the passed tag is null
    118      */
    119     @NonNull
    120     public TabSpec newTabSpec(@NonNull String tag) {
    121         if (tag == null) {
    122             throw new IllegalArgumentException("tag must be non-null");
    123         }
    124         return new TabSpec(tag);
    125     }
    126 
    127 
    128 
    129     /**
    130       * <p>Call setup() before adding tabs if loading TabHost using findViewById().
    131       * <i><b>However</i></b>: You do not need to call setup() after getTabHost()
    132       * in {@link android.app.TabActivity TabActivity}.
    133       * Example:</p>
    134 <pre>mTabHost = (TabHost)findViewById(R.id.tabhost);
    135 mTabHost.setup();
    136 mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
    137       */
    138     public void setup() {
    139         mTabWidget = findViewById(com.android.internal.R.id.tabs);
    140         if (mTabWidget == null) {
    141             throw new RuntimeException(
    142                     "Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'");
    143         }
    144 
    145         // KeyListener to attach to all tabs. Detects non-navigation keys
    146         // and relays them to the tab content.
    147         mTabKeyListener = new OnKeyListener() {
    148             public boolean onKey(View v, int keyCode, KeyEvent event) {
    149                 switch (keyCode) {
    150                     case KeyEvent.KEYCODE_DPAD_CENTER:
    151                     case KeyEvent.KEYCODE_DPAD_LEFT:
    152                     case KeyEvent.KEYCODE_DPAD_RIGHT:
    153                     case KeyEvent.KEYCODE_DPAD_UP:
    154                     case KeyEvent.KEYCODE_DPAD_DOWN:
    155                     case KeyEvent.KEYCODE_ENTER:
    156                         return false;
    157 
    158                 }
    159                 mTabContent.requestFocus(View.FOCUS_FORWARD);
    160                 return mTabContent.dispatchKeyEvent(event);
    161             }
    162 
    163         };
    164 
    165         mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged() {
    166             public void onTabSelectionChanged(int tabIndex, boolean clicked) {
    167                 setCurrentTab(tabIndex);
    168                 if (clicked) {
    169                     mTabContent.requestFocus(View.FOCUS_FORWARD);
    170                 }
    171             }
    172         });
    173 
    174         mTabContent = findViewById(com.android.internal.R.id.tabcontent);
    175         if (mTabContent == null) {
    176             throw new RuntimeException(
    177                     "Your TabHost must have a FrameLayout whose id attribute is "
    178                             + "'android.R.id.tabcontent'");
    179         }
    180     }
    181 
    182     /** @hide */
    183     @Override
    184     public void sendAccessibilityEventInternal(int eventType) {
    185         /* avoid super class behavior - TabWidget sends the right events */
    186     }
    187 
    188     /**
    189      * If you are using {@link TabSpec#setContent(android.content.Intent)}, this
    190      * must be called since the activityGroup is needed to launch the local activity.
    191      *
    192      * This is done for you if you extend {@link android.app.TabActivity}.
    193      * @param activityGroup Used to launch activities for tab content.
    194      */
    195     public void setup(LocalActivityManager activityGroup) {
    196         setup();
    197         mLocalActivityManager = activityGroup;
    198     }
    199 
    200     @Override
    201     public void onTouchModeChanged(boolean isInTouchMode) {
    202         // No longer used, but kept to maintain API compatibility.
    203     }
    204 
    205     /**
    206      * Add a tab.
    207      * @param tabSpec Specifies how to create the indicator and content.
    208      * @throws IllegalArgumentException If the passed tab spec has null indicator strategy and / or
    209      *      null content strategy.
    210      */
    211     public void addTab(TabSpec tabSpec) {
    212 
    213         if (tabSpec.mIndicatorStrategy == null) {
    214             throw new IllegalArgumentException("you must specify a way to create the tab indicator.");
    215         }
    216 
    217         if (tabSpec.mContentStrategy == null) {
    218             throw new IllegalArgumentException("you must specify a way to create the tab content");
    219         }
    220         View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
    221         tabIndicator.setOnKeyListener(mTabKeyListener);
    222 
    223         // If this is a custom view, then do not draw the bottom strips for
    224         // the tab indicators.
    225         if (tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy) {
    226             mTabWidget.setStripEnabled(false);
    227         }
    228 
    229         mTabWidget.addView(tabIndicator);
    230         mTabSpecs.add(tabSpec);
    231 
    232         if (mCurrentTab == -1) {
    233             setCurrentTab(0);
    234         }
    235     }
    236 
    237 
    238     /**
    239      * Removes all tabs from the tab widget associated with this tab host.
    240      */
    241     public void clearAllTabs() {
    242         mTabWidget.removeAllViews();
    243         initTabHost();
    244         mTabContent.removeAllViews();
    245         mTabSpecs.clear();
    246         requestLayout();
    247         invalidate();
    248     }
    249 
    250     public TabWidget getTabWidget() {
    251         return mTabWidget;
    252     }
    253 
    254     /**
    255      * Returns the current tab.
    256      *
    257      * @return the current tab, may be {@code null} if no tab is set as current
    258      */
    259     @Nullable
    260     public int getCurrentTab() {
    261         return mCurrentTab;
    262     }
    263 
    264     /**
    265      * Returns the tag for the current tab.
    266      *
    267      * @return the tag for the current tab, may be {@code null} if no tab is
    268      *         set as current
    269      */
    270     @Nullable
    271     public String getCurrentTabTag() {
    272         if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
    273             return mTabSpecs.get(mCurrentTab).getTag();
    274         }
    275         return null;
    276     }
    277 
    278     /**
    279      * Returns the view for the current tab.
    280      *
    281      * @return the view for the current tab, may be {@code null} if no tab is
    282      *         set as current
    283      */
    284     @Nullable
    285     public View getCurrentTabView() {
    286         if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
    287             return mTabWidget.getChildTabViewAt(mCurrentTab);
    288         }
    289         return null;
    290     }
    291 
    292     public View getCurrentView() {
    293         return mCurrentView;
    294     }
    295 
    296     /**
    297      * Sets the current tab based on its tag.
    298      *
    299      * @param tag the tag for the tab to set as current
    300      */
    301     public void setCurrentTabByTag(String tag) {
    302         for (int i = 0, count = mTabSpecs.size(); i < count; i++) {
    303             if (mTabSpecs.get(i).getTag().equals(tag)) {
    304                 setCurrentTab(i);
    305                 break;
    306             }
    307         }
    308     }
    309 
    310     /**
    311      * Get the FrameLayout which holds tab content
    312      */
    313     public FrameLayout getTabContentView() {
    314         return mTabContent;
    315     }
    316 
    317     /**
    318      * Get the location of the TabWidget.
    319      *
    320      * @return The TabWidget location.
    321      */
    322     private int getTabWidgetLocation() {
    323         int location = TABWIDGET_LOCATION_TOP;
    324 
    325         switch (mTabWidget.getOrientation()) {
    326             case LinearLayout.VERTICAL:
    327                 location = (mTabContent.getLeft() < mTabWidget.getLeft()) ? TABWIDGET_LOCATION_RIGHT
    328                         : TABWIDGET_LOCATION_LEFT;
    329                 break;
    330             case LinearLayout.HORIZONTAL:
    331             default:
    332                 location = (mTabContent.getTop() < mTabWidget.getTop()) ? TABWIDGET_LOCATION_BOTTOM
    333                         : TABWIDGET_LOCATION_TOP;
    334                 break;
    335         }
    336         return location;
    337     }
    338 
    339     @Override
    340     public boolean dispatchKeyEvent(KeyEvent event) {
    341         final boolean handled = super.dispatchKeyEvent(event);
    342 
    343         // unhandled key events change focus to tab indicator for embedded
    344         // activities when there is nothing that will take focus from default
    345         // focus searching
    346         if (!handled
    347                 && (event.getAction() == KeyEvent.ACTION_DOWN)
    348                 && (mCurrentView != null)
    349                 && (mCurrentView.isRootNamespace())
    350                 && (mCurrentView.hasFocus())) {
    351             int keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_UP;
    352             int directionShouldChangeFocus = View.FOCUS_UP;
    353             int soundEffect = SoundEffectConstants.NAVIGATION_UP;
    354 
    355             switch (getTabWidgetLocation()) {
    356                 case TABWIDGET_LOCATION_LEFT:
    357                     keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_LEFT;
    358                     directionShouldChangeFocus = View.FOCUS_LEFT;
    359                     soundEffect = SoundEffectConstants.NAVIGATION_LEFT;
    360                     break;
    361                 case TABWIDGET_LOCATION_RIGHT:
    362                     keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_RIGHT;
    363                     directionShouldChangeFocus = View.FOCUS_RIGHT;
    364                     soundEffect = SoundEffectConstants.NAVIGATION_RIGHT;
    365                     break;
    366                 case TABWIDGET_LOCATION_BOTTOM:
    367                     keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_DOWN;
    368                     directionShouldChangeFocus = View.FOCUS_DOWN;
    369                     soundEffect = SoundEffectConstants.NAVIGATION_DOWN;
    370                     break;
    371                 case TABWIDGET_LOCATION_TOP:
    372                 default:
    373                     keyCodeShouldChangeFocus = KeyEvent.KEYCODE_DPAD_UP;
    374                     directionShouldChangeFocus = View.FOCUS_UP;
    375                     soundEffect = SoundEffectConstants.NAVIGATION_UP;
    376                     break;
    377             }
    378             if (event.getKeyCode() == keyCodeShouldChangeFocus
    379                     && mCurrentView.findFocus().focusSearch(directionShouldChangeFocus) == null) {
    380                 mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
    381                 playSoundEffect(soundEffect);
    382                 return true;
    383             }
    384         }
    385         return handled;
    386     }
    387 
    388 
    389     @Override
    390     public void dispatchWindowFocusChanged(boolean hasFocus) {
    391         if (mCurrentView != null){
    392             mCurrentView.dispatchWindowFocusChanged(hasFocus);
    393         }
    394     }
    395 
    396     @Override
    397     public CharSequence getAccessibilityClassName() {
    398         return TabHost.class.getName();
    399     }
    400 
    401     public void setCurrentTab(int index) {
    402         if (index < 0 || index >= mTabSpecs.size()) {
    403             return;
    404         }
    405 
    406         if (index == mCurrentTab) {
    407             return;
    408         }
    409 
    410         // notify old tab content
    411         if (mCurrentTab != -1) {
    412             mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed();
    413         }
    414 
    415         mCurrentTab = index;
    416         final TabHost.TabSpec spec = mTabSpecs.get(index);
    417 
    418         // Call the tab widget's focusCurrentTab(), instead of just
    419         // selecting the tab.
    420         mTabWidget.focusCurrentTab(mCurrentTab);
    421 
    422         // tab content
    423         mCurrentView = spec.mContentStrategy.getContentView();
    424 
    425         if (mCurrentView.getParent() == null) {
    426             mTabContent
    427                     .addView(
    428                             mCurrentView,
    429                             new ViewGroup.LayoutParams(
    430                                     ViewGroup.LayoutParams.MATCH_PARENT,
    431                                     ViewGroup.LayoutParams.MATCH_PARENT));
    432         }
    433 
    434         if (!mTabWidget.hasFocus()) {
    435             // if the tab widget didn't take focus (likely because we're in touch mode)
    436             // give the current tab content view a shot
    437             mCurrentView.requestFocus();
    438         }
    439 
    440         //mTabContent.requestFocus(View.FOCUS_FORWARD);
    441         invokeOnTabChangeListener();
    442     }
    443 
    444     /**
    445      * Register a callback to be invoked when the selected state of any of the items
    446      * in this list changes
    447      * @param l
    448      * The callback that will run
    449      */
    450     public void setOnTabChangedListener(OnTabChangeListener l) {
    451         mOnTabChangeListener = l;
    452     }
    453 
    454     private void invokeOnTabChangeListener() {
    455         if (mOnTabChangeListener != null) {
    456             mOnTabChangeListener.onTabChanged(getCurrentTabTag());
    457         }
    458     }
    459 
    460     /**
    461      * Interface definition for a callback to be invoked when tab changed
    462      */
    463     public interface OnTabChangeListener {
    464         void onTabChanged(String tabId);
    465     }
    466 
    467 
    468     /**
    469      * Makes the content of a tab when it is selected. Use this if your tab
    470      * content needs to be created on demand, i.e. you are not showing an
    471      * existing view or starting an activity.
    472      */
    473     public interface TabContentFactory {
    474         /**
    475          * Callback to make the tab contents
    476          *
    477          * @param tag
    478          *            Which tab was selected.
    479          * @return The view to display the contents of the selected tab.
    480          */
    481         View createTabContent(String tag);
    482     }
    483 
    484 
    485     /**
    486      * A tab has a tab indicator, content, and a tag that is used to keep
    487      * track of it.  This builder helps choose among these options.
    488      *
    489      * For the tab indicator, your choices are:
    490      * 1) set a label
    491      * 2) set a label and an icon
    492      *
    493      * For the tab content, your choices are:
    494      * 1) the id of a {@link View}
    495      * 2) a {@link TabContentFactory} that creates the {@link View} content.
    496      * 3) an {@link Intent} that launches an {@link android.app.Activity}.
    497      */
    498     public class TabSpec {
    499 
    500         private final @NonNull String mTag;
    501 
    502         private IndicatorStrategy mIndicatorStrategy;
    503         private ContentStrategy mContentStrategy;
    504 
    505         /**
    506          * Constructs a new tab specification with the specified tag.
    507          *
    508          * @param tag the tag for the tag specification, must be non-null
    509          */
    510         private TabSpec(@NonNull String tag) {
    511             mTag = tag;
    512         }
    513 
    514         /**
    515          * Specify a label as the tab indicator.
    516          */
    517         public TabSpec setIndicator(CharSequence label) {
    518             mIndicatorStrategy = new LabelIndicatorStrategy(label);
    519             return this;
    520         }
    521 
    522         /**
    523          * Specify a label and icon as the tab indicator.
    524          */
    525         public TabSpec setIndicator(CharSequence label, Drawable icon) {
    526             mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
    527             return this;
    528         }
    529 
    530         /**
    531          * Specify a view as the tab indicator.
    532          */
    533         public TabSpec setIndicator(View view) {
    534             mIndicatorStrategy = new ViewIndicatorStrategy(view);
    535             return this;
    536         }
    537 
    538         /**
    539          * Specify the id of the view that should be used as the content
    540          * of the tab.
    541          */
    542         public TabSpec setContent(int viewId) {
    543             mContentStrategy = new ViewIdContentStrategy(viewId);
    544             return this;
    545         }
    546 
    547         /**
    548          * Specify a {@link android.widget.TabHost.TabContentFactory} to use to
    549          * create the content of the tab.
    550          */
    551         public TabSpec setContent(TabContentFactory contentFactory) {
    552             mContentStrategy = new FactoryContentStrategy(mTag, contentFactory);
    553             return this;
    554         }
    555 
    556         /**
    557          * Specify an intent to use to launch an activity as the tab content.
    558          */
    559         public TabSpec setContent(Intent intent) {
    560             mContentStrategy = new IntentContentStrategy(mTag, intent);
    561             return this;
    562         }
    563 
    564         /**
    565          * Returns the tag for this tab specification.
    566          *
    567          * @return the tag for this tab specification
    568          */
    569         @NonNull
    570         public String getTag() {
    571             return mTag;
    572         }
    573     }
    574 
    575     /**
    576      * Specifies what you do to create a tab indicator.
    577      */
    578     private static interface IndicatorStrategy {
    579 
    580         /**
    581          * Return the view for the indicator.
    582          */
    583         View createIndicatorView();
    584     }
    585 
    586     /**
    587      * Specifies what you do to manage the tab content.
    588      */
    589     private static interface ContentStrategy {
    590 
    591         /**
    592          * Return the content view.  The view should may be cached locally.
    593          */
    594         View getContentView();
    595 
    596         /**
    597          * Perhaps do something when the tab associated with this content has
    598          * been closed (i.e make it invisible, or remove it).
    599          */
    600         void tabClosed();
    601     }
    602 
    603     /**
    604      * How to create a tab indicator that just has a label.
    605      */
    606     private class LabelIndicatorStrategy implements IndicatorStrategy {
    607 
    608         private final CharSequence mLabel;
    609 
    610         private LabelIndicatorStrategy(CharSequence label) {
    611             mLabel = label;
    612         }
    613 
    614         public View createIndicatorView() {
    615             final Context context = getContext();
    616             LayoutInflater inflater =
    617                     (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    618             View tabIndicator = inflater.inflate(mTabLayoutId,
    619                     mTabWidget, // tab widget is the parent
    620                     false); // no inflate params
    621 
    622             final TextView tv = tabIndicator.findViewById(R.id.title);
    623             tv.setText(mLabel);
    624 
    625             if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
    626                 // Donut apps get old color scheme
    627                 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
    628                 tv.setTextColor(context.getColorStateList(R.color.tab_indicator_text_v4));
    629             }
    630 
    631             return tabIndicator;
    632         }
    633     }
    634 
    635     /**
    636      * How we create a tab indicator that has a label and an icon
    637      */
    638     private class LabelAndIconIndicatorStrategy implements IndicatorStrategy {
    639 
    640         private final CharSequence mLabel;
    641         private final Drawable mIcon;
    642 
    643         private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
    644             mLabel = label;
    645             mIcon = icon;
    646         }
    647 
    648         public View createIndicatorView() {
    649             final Context context = getContext();
    650             LayoutInflater inflater =
    651                     (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    652             View tabIndicator = inflater.inflate(mTabLayoutId,
    653                     mTabWidget, // tab widget is the parent
    654                     false); // no inflate params
    655 
    656             final TextView tv = tabIndicator.findViewById(R.id.title);
    657             final ImageView iconView = tabIndicator.findViewById(R.id.icon);
    658 
    659             // when icon is gone by default, we're in exclusive mode
    660             final boolean exclusive = iconView.getVisibility() == View.GONE;
    661             final boolean bindIcon = !exclusive || TextUtils.isEmpty(mLabel);
    662 
    663             tv.setText(mLabel);
    664 
    665             if (bindIcon && mIcon != null) {
    666                 iconView.setImageDrawable(mIcon);
    667                 iconView.setVisibility(VISIBLE);
    668             }
    669 
    670             if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
    671                 // Donut apps get old color scheme
    672                 tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
    673                 tv.setTextColor(context.getColorStateList(R.color.tab_indicator_text_v4));
    674             }
    675 
    676             return tabIndicator;
    677         }
    678     }
    679 
    680     /**
    681      * How to create a tab indicator by specifying a view.
    682      */
    683     private class ViewIndicatorStrategy implements IndicatorStrategy {
    684 
    685         private final View mView;
    686 
    687         private ViewIndicatorStrategy(View view) {
    688             mView = view;
    689         }
    690 
    691         public View createIndicatorView() {
    692             return mView;
    693         }
    694     }
    695 
    696     /**
    697      * How to create the tab content via a view id.
    698      */
    699     private class ViewIdContentStrategy implements ContentStrategy {
    700 
    701         private final View mView;
    702 
    703         private ViewIdContentStrategy(int viewId) {
    704             mView = mTabContent.findViewById(viewId);
    705             if (mView != null) {
    706                 mView.setVisibility(View.GONE);
    707             } else {
    708                 throw new RuntimeException("Could not create tab content because " +
    709                         "could not find view with id " + viewId);
    710             }
    711         }
    712 
    713         public View getContentView() {
    714             mView.setVisibility(View.VISIBLE);
    715             return mView;
    716         }
    717 
    718         public void tabClosed() {
    719             mView.setVisibility(View.GONE);
    720         }
    721     }
    722 
    723     /**
    724      * How tab content is managed using {@link TabContentFactory}.
    725      */
    726     private class FactoryContentStrategy implements ContentStrategy {
    727         private View mTabContent;
    728         private final CharSequence mTag;
    729         private TabContentFactory mFactory;
    730 
    731         public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
    732             mTag = tag;
    733             mFactory = factory;
    734         }
    735 
    736         public View getContentView() {
    737             if (mTabContent == null) {
    738                 mTabContent = mFactory.createTabContent(mTag.toString());
    739             }
    740             mTabContent.setVisibility(View.VISIBLE);
    741             return mTabContent;
    742         }
    743 
    744         public void tabClosed() {
    745             mTabContent.setVisibility(View.GONE);
    746         }
    747     }
    748 
    749     /**
    750      * How tab content is managed via an {@link Intent}: the content view is the
    751      * decorview of the launched activity.
    752      */
    753     private class IntentContentStrategy implements ContentStrategy {
    754 
    755         private final String mTag;
    756         private final Intent mIntent;
    757 
    758         private View mLaunchedView;
    759 
    760         private IntentContentStrategy(String tag, Intent intent) {
    761             mTag = tag;
    762             mIntent = intent;
    763         }
    764 
    765         public View getContentView() {
    766             if (mLocalActivityManager == null) {
    767                 throw new IllegalStateException("Did you forget to call 'public void setup(LocalActivityManager activityGroup)'?");
    768             }
    769             final Window w = mLocalActivityManager.startActivity(
    770                     mTag, mIntent);
    771             final View wd = w != null ? w.getDecorView() : null;
    772             if (mLaunchedView != wd && mLaunchedView != null) {
    773                 if (mLaunchedView.getParent() != null) {
    774                     mTabContent.removeView(mLaunchedView);
    775                 }
    776             }
    777             mLaunchedView = wd;
    778 
    779             // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activities for now so they can get
    780             // focus if none of their children have it. They need focus to be able to
    781             // display menu items.
    782             //
    783             // Replace this with something better when Bug 628886 is fixed...
    784             //
    785             if (mLaunchedView != null) {
    786                 mLaunchedView.setVisibility(View.VISIBLE);
    787                 mLaunchedView.setFocusableInTouchMode(true);
    788                 ((ViewGroup) mLaunchedView).setDescendantFocusability(
    789                         FOCUS_AFTER_DESCENDANTS);
    790             }
    791             return mLaunchedView;
    792         }
    793 
    794         public void tabClosed() {
    795             if (mLaunchedView != null) {
    796                 mLaunchedView.setVisibility(View.GONE);
    797             }
    798         }
    799     }
    800 
    801 }
    802