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