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
17package android.widget;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.app.LocalActivityManager;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.TypedArray;
25import android.graphics.drawable.Drawable;
26import android.os.Build;
27import android.text.TextUtils;
28import android.util.AttributeSet;
29import android.view.KeyEvent;
30import android.view.LayoutInflater;
31import android.view.SoundEffectConstants;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.ViewTreeObserver;
35import android.view.Window;
36
37import com.android.internal.R;
38
39import java.util.ArrayList;
40import 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 */
49public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchModeChangeListener {
50
51    private static final int TABWIDGET_LOCATION_LEFT = 0;
52    private static final int TABWIDGET_LOCATION_TOP = 1;
53    private static final int TABWIDGET_LOCATION_RIGHT = 2;
54    private static final int TABWIDGET_LOCATION_BOTTOM = 3;
55    private TabWidget mTabWidget;
56    private FrameLayout mTabContent;
57    private List<TabSpec> mTabSpecs = new ArrayList<TabSpec>(2);
58    /**
59     * This field should be made private, so it is hidden from the SDK.
60     * {@hide}
61     */
62    protected int mCurrentTab = -1;
63    private View mCurrentView = null;
64    /**
65     * This field should be made private, so it is hidden from the SDK.
66     * {@hide}
67     */
68    protected LocalActivityManager mLocalActivityManager = null;
69    private OnTabChangeListener mOnTabChangeListener;
70    private OnKeyListener mTabKeyListener;
71
72    private int mTabLayoutId;
73
74    public TabHost(Context context) {
75        super(context);
76        initTabHost();
77    }
78
79    public TabHost(Context context, AttributeSet attrs) {
80        this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
81    }
82
83    public TabHost(Context context, AttributeSet attrs, int defStyleAttr) {
84        this(context, attrs, defStyleAttr, 0);
85    }
86
87    public TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
88        super(context, attrs);
89
90        final TypedArray a = context.obtainStyledAttributes(
91                attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes);
92
93        mTabLayoutId = a.getResourceId(R.styleable.TabWidget_tabLayout, 0);
94        a.recycle();
95
96        if (mTabLayoutId == 0) {
97            // In case the tabWidgetStyle does not inherit from Widget.TabWidget and tabLayout is
98            // not defined.
99            mTabLayoutId = R.layout.tab_indicator_holo;
100        }
101
102        initTabHost();
103    }
104
105    private void initTabHost() {
106        setFocusableInTouchMode(true);
107        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
108
109        mCurrentTab = -1;
110        mCurrentView = null;
111    }
112
113    /**
114     * Creates a new {@link TabSpec} associated with this tab host.
115     *
116     * @param tag tag for the tab specification, must be non-null
117     * @throws IllegalArgumentException If the passed tag is null
118     */
119    @NonNull
120    public TabSpec newTabSpec(@NonNull String tag) {
121        if (tag == null) {
122            throw new IllegalArgumentException("tag must be non-null");
123        }
124        return new TabSpec(tag);
125    }
126
127
128
129    /**
130      * <p>Call setup() before adding tabs if loading TabHost using findViewById().
131      * <i><b>However</i></b>: You do not need to call setup() after getTabHost()
132      * in {@link android.app.TabActivity TabActivity}.
133      * Example:</p>
134<pre>mTabHost = (TabHost)findViewById(R.id.tabhost);
135mTabHost.setup();
136mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
137      */
138    public void setup() {
139        mTabWidget = findViewById(com.android.internal.R.id.tabs);
140        if (mTabWidget == null) {
141            throw new RuntimeException(
142                    "Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'");
143        }
144
145        // KeyListener to attach to all tabs. Detects non-navigation keys
146        // and relays them to the tab content.
147        mTabKeyListener = new OnKeyListener() {
148            public boolean onKey(View v, int keyCode, KeyEvent event) {
149                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
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     */
200    public void setup(LocalActivityManager activityGroup) {
201        setup();
202        mLocalActivityManager = activityGroup;
203    }
204
205    @Override
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     */
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     */
246    public void clearAllTabs() {
247        mTabWidget.removeAllViews();
248        initTabHost();
249        mTabContent.removeAllViews();
250        mTabSpecs.clear();
251        requestLayout();
252        invalidate();
253    }
254
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
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
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
290    public View getCurrentTabView() {
291        if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
292            return mTabWidget.getChildTabViewAt(mCurrentTab);
293        }
294        return null;
295    }
296
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     */
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     */
318    public FrameLayout getTabContentView() {
319        return mTabContent;
320    }
321
322    /**
323     * Get the location of the TabWidget.
324     *
325     * @return The TabWidget location.
326     */
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
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
395    public void dispatchWindowFocusChanged(boolean hasFocus) {
396        if (mCurrentView != null){
397            mCurrentView.dispatchWindowFocusChanged(hasFocus);
398        }
399    }
400
401    @Override
402    public CharSequence getAccessibilityClassName() {
403        return TabHost.class.getName();
404    }
405
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     */
455    public void setOnTabChangedListener(OnTabChangeListener l) {
456        mOnTabChangeListener = l;
457    }
458
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 {
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         */
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         */
515        private TabSpec(@NonNull String tag) {
516            mTag = tag;
517        }
518
519        /**
520         * Specify a label as the tab indicator.
521         */
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         */
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         */
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         */
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         */
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         */
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
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         */
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         */
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         */
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
615        private LabelIndicatorStrategy(CharSequence label) {
616            mLabel = label;
617        }
618
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
648        private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
649            mLabel = label;
650            mIcon = icon;
651        }
652
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
692        private ViewIndicatorStrategy(View view) {
693            mView = view;
694        }
695
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
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
718        public View getContentView() {
719            mView.setVisibility(View.VISIBLE);
720            return mView;
721        }
722
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
736        public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
737            mTag = tag;
738            mFactory = factory;
739        }
740
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
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
765        private IntentContentStrategy(String tag, Intent intent) {
766            mTag = tag;
767            mIntent = intent;
768        }
769
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
799        public void tabClosed() {
800            if (mLaunchedView != null) {
801                mLaunchedView.setVisibility(View.GONE);
802            }
803        }
804    }
805
806}
807