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