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.compat.annotation.UnsupportedAppUsage;
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 
TabHost(Context context)78     public TabHost(Context context) {
79         super(context);
80         initTabHost();
81     }
82 
TabHost(Context context, AttributeSet attrs)83     public TabHost(Context context, AttributeSet attrs) {
84         this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
85     }
86 
TabHost(Context context, AttributeSet attrs, int defStyleAttr)87     public TabHost(Context context, AttributeSet attrs, int defStyleAttr) {
88         this(context, attrs, defStyleAttr, 0);
89     }
90 
TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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 
initTabHost()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
newTabSpec(@onNull String tag)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       */
setup()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
sendAccessibilityEventInternal(int eventType)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      */
setup(LocalActivityManager activityGroup)206     public void setup(LocalActivityManager activityGroup) {
207         setup();
208         mLocalActivityManager = activityGroup;
209     }
210 
211     @Override
onTouchModeChanged(boolean isInTouchMode)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      */
addTab(TabSpec tabSpec)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      */
clearAllTabs()252     public void clearAllTabs() {
253         mTabWidget.removeAllViews();
254         initTabHost();
255         mTabContent.removeAllViews();
256         mTabSpecs.clear();
257         requestLayout();
258         invalidate();
259     }
260 
getTabWidget()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
getCurrentTab()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
getCurrentTabTag()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
getCurrentTabView()296     public View getCurrentTabView() {
297         if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
298             return mTabWidget.getChildTabViewAt(mCurrentTab);
299         }
300         return null;
301     }
302 
getCurrentView()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      */
setCurrentTabByTag(String tag)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      */
getTabContentView()324     public FrameLayout getTabContentView() {
325         return mTabContent;
326     }
327 
328     /**
329      * Get the location of the TabWidget.
330      *
331      * @return The TabWidget location.
332      */
getTabWidgetLocation()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
dispatchKeyEvent(KeyEvent event)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
dispatchWindowFocusChanged(boolean hasFocus)401     public void dispatchWindowFocusChanged(boolean hasFocus) {
402         if (mCurrentView != null){
403             mCurrentView.dispatchWindowFocusChanged(hasFocus);
404         }
405     }
406 
407     @Override
getAccessibilityClassName()408     public CharSequence getAccessibilityClassName() {
409         return TabHost.class.getName();
410     }
411 
setCurrentTab(int index)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      */
setOnTabChangedListener(OnTabChangeListener l)461     public void setOnTabChangedListener(OnTabChangeListener l) {
462         mOnTabChangeListener = l;
463     }
464 
invokeOnTabChangeListener()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 {
onTabChanged(String tabId)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          */
createTabContent(String tag)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          */
TabSpec(@onNull String tag)523         private TabSpec(@NonNull String tag) {
524             mTag = tag;
525         }
526 
527         /**
528          * Specify a label as the tab indicator.
529          */
setIndicator(CharSequence label)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          */
setIndicator(CharSequence label, Drawable icon)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          */
setIndicator(View view)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          */
setContent(int viewId)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          */
setContent(TabContentFactory contentFactory)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          */
setContent(Intent intent)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
getTag()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          */
createIndicatorView()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          */
getContentView()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          */
tabClosed()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 
LabelIndicatorStrategy(CharSequence label)623         private LabelIndicatorStrategy(CharSequence label) {
624             mLabel = label;
625         }
626 
createIndicatorView()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 
LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon)656         private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
657             mLabel = label;
658             mIcon = icon;
659         }
660 
createIndicatorView()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 
ViewIndicatorStrategy(View view)700         private ViewIndicatorStrategy(View view) {
701             mView = view;
702         }
703 
createIndicatorView()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 
ViewIdContentStrategy(int viewId)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 
getContentView()726         public View getContentView() {
727             mView.setVisibility(View.VISIBLE);
728             return mView;
729         }
730 
tabClosed()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 
FactoryContentStrategy(CharSequence tag, TabContentFactory factory)744         public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {
745             mTag = tag;
746             mFactory = factory;
747         }
748 
getContentView()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 
tabClosed()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 
IntentContentStrategy(String tag, Intent intent)773         private IntentContentStrategy(String tag, Intent intent) {
774             mTag = tag;
775             mIntent = intent;
776         }
777 
778         @UnsupportedAppUsage
getContentView()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
tabClosed()809         public void tabClosed() {
810             if (mLaunchedView != null) {
811                 mLaunchedView.setVisibility(View.GONE);
812             }
813         }
814     }
815 
816 }
817