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