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 
TabHost(Context context)74     public TabHost(Context context) {
75         super(context);
76         initTabHost();
77     }
78 
TabHost(Context context, AttributeSet attrs)79     public TabHost(Context context, AttributeSet attrs) {
80         this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
81     }
82 
TabHost(Context context, AttributeSet attrs, int defStyleAttr)83     public TabHost(Context context, AttributeSet attrs, int defStyleAttr) {
84         this(context, attrs, defStyleAttr, 0);
85     }
86 
TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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 
initTabHost()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
newTabSpec(@onNull String tag)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       */
setup()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
sendAccessibilityEventInternal(int eventType)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      */
setup(LocalActivityManager activityGroup)195     public void setup(LocalActivityManager activityGroup) {
196         setup();
197         mLocalActivityManager = activityGroup;
198     }
199 
200     @Override
onTouchModeChanged(boolean isInTouchMode)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      */
addTab(TabSpec tabSpec)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      */
clearAllTabs()241     public void clearAllTabs() {
242         mTabWidget.removeAllViews();
243         initTabHost();
244         mTabContent.removeAllViews();
245         mTabSpecs.clear();
246         requestLayout();
247         invalidate();
248     }
249 
getTabWidget()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
getCurrentTab()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
getCurrentTabTag()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
getCurrentTabView()285     public View getCurrentTabView() {
286         if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
287             return mTabWidget.getChildTabViewAt(mCurrentTab);
288         }
289         return null;
290     }
291 
getCurrentView()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      */
setCurrentTabByTag(String tag)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      */
getTabContentView()313     public FrameLayout getTabContentView() {
314         return mTabContent;
315     }
316 
317     /**
318      * Get the location of the TabWidget.
319      *
320      * @return The TabWidget location.
321      */
getTabWidgetLocation()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
dispatchKeyEvent(KeyEvent event)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
dispatchWindowFocusChanged(boolean hasFocus)390     public void dispatchWindowFocusChanged(boolean hasFocus) {
391         if (mCurrentView != null){
392             mCurrentView.dispatchWindowFocusChanged(hasFocus);
393         }
394     }
395 
396     @Override
getAccessibilityClassName()397     public CharSequence getAccessibilityClassName() {
398         return TabHost.class.getName();
399     }
400 
setCurrentTab(int index)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      */
setOnTabChangedListener(OnTabChangeListener l)450     public void setOnTabChangedListener(OnTabChangeListener l) {
451         mOnTabChangeListener = l;
452     }
453 
invokeOnTabChangeListener()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 {
onTabChanged(String tabId)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          */
createTabContent(String tag)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          */
TabSpec(@onNull String tag)510         private TabSpec(@NonNull String tag) {
511             mTag = tag;
512         }
513 
514         /**
515          * Specify a label as the tab indicator.
516          */
setIndicator(CharSequence label)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          */
setIndicator(CharSequence label, Drawable icon)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          */
setIndicator(View view)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          */
setContent(int viewId)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          */
setContent(TabContentFactory contentFactory)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          */
setContent(Intent intent)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
getTag()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          */
createIndicatorView()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          */
getContentView()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          */
tabClosed()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 
LabelIndicatorStrategy(CharSequence label)610         private LabelIndicatorStrategy(CharSequence label) {
611             mLabel = label;
612         }
613 
createIndicatorView()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 
LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon)643         private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
644             mLabel = label;
645             mIcon = icon;
646         }
647 
createIndicatorView()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 
ViewIndicatorStrategy(View view)687         private ViewIndicatorStrategy(View view) {
688             mView = view;
689         }
690 
createIndicatorView()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 
ViewIdContentStrategy(int viewId)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 
getContentView()713         public View getContentView() {
714             mView.setVisibility(View.VISIBLE);
715             return mView;
716         }
717 
tabClosed()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 
FactoryContentStrategy(CharSequence tag, TabContentFactory factory)731         public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
732             mTag = tag;
733             mFactory = factory;
734         }
735 
getContentView()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 
tabClosed()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 
IntentContentStrategy(String tag, Intent intent)760         private IntentContentStrategy(String tag, Intent intent) {
761             mTag = tag;
762             mIntent = intent;
763         }
764 
getContentView()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 
tabClosed()794         public void tabClosed() {
795             if (mLaunchedView != null) {
796                 mLaunchedView.setVisibility(View.GONE);
797             }
798         }
799     }
800 
801 }
802