WindowDecorActionBar.java revision 3abf5c4bd167547b7e995f1b14ae1bae210c5fec
1/* 2 * Copyright (C) 2010 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 com.android.internal.app; 18 19import android.animation.ValueAnimator; 20import android.content.res.TypedArray; 21import android.view.ViewParent; 22import android.widget.Toolbar; 23import com.android.internal.R; 24import com.android.internal.view.ActionBarPolicy; 25import com.android.internal.view.menu.MenuBuilder; 26import com.android.internal.view.menu.MenuPopupHelper; 27import com.android.internal.view.menu.SubMenuBuilder; 28import com.android.internal.widget.ActionBarContainer; 29import com.android.internal.widget.ActionBarContextView; 30import com.android.internal.widget.ActionBarOverlayLayout; 31import com.android.internal.widget.DecorToolbar; 32import com.android.internal.widget.ScrollingTabContainerView; 33 34import android.animation.Animator; 35import android.animation.Animator.AnimatorListener; 36import android.animation.AnimatorListenerAdapter; 37import android.animation.AnimatorSet; 38import android.animation.ObjectAnimator; 39import android.app.ActionBar; 40import android.app.Activity; 41import android.app.Dialog; 42import android.app.FragmentTransaction; 43import android.content.Context; 44import android.content.res.Configuration; 45import android.content.res.Resources; 46import android.graphics.drawable.Drawable; 47import android.util.TypedValue; 48import android.view.ActionMode; 49import android.view.ContextThemeWrapper; 50import android.view.LayoutInflater; 51import android.view.Menu; 52import android.view.MenuInflater; 53import android.view.MenuItem; 54import android.view.View; 55import android.view.Window; 56import android.view.accessibility.AccessibilityEvent; 57import android.view.animation.AnimationUtils; 58import android.widget.SpinnerAdapter; 59 60import java.lang.ref.WeakReference; 61import java.util.ArrayList; 62 63/** 64 * WindowDecorActionBar is the ActionBar implementation used 65 * by devices of all screen sizes as part of the window decor layout. 66 * If it detects a compatible decor, it will split contextual modes 67 * across both the ActionBarView at the top of the screen and 68 * a horizontal LinearLayout at the bottom which is normally hidden. 69 */ 70public class WindowDecorActionBar extends ActionBar implements 71 ActionBarOverlayLayout.ActionBarVisibilityCallback { 72 private static final String TAG = "WindowDecorActionBar"; 73 74 private Context mContext; 75 private Context mThemedContext; 76 private Activity mActivity; 77 private Dialog mDialog; 78 79 private ActionBarOverlayLayout mOverlayLayout; 80 private ActionBarContainer mContainerView; 81 private DecorToolbar mDecorToolbar; 82 private ActionBarContextView mContextView; 83 private ActionBarContainer mSplitView; 84 private View mContentView; 85 private ScrollingTabContainerView mTabScrollView; 86 87 private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>(); 88 89 private TabImpl mSelectedTab; 90 private int mSavedTabPosition = INVALID_POSITION; 91 92 private boolean mDisplayHomeAsUpSet; 93 94 ActionModeImpl mActionMode; 95 ActionMode mDeferredDestroyActionMode; 96 ActionMode.Callback mDeferredModeDestroyCallback; 97 98 private boolean mLastMenuVisibility; 99 private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners = 100 new ArrayList<OnMenuVisibilityListener>(); 101 102 private static final int CONTEXT_DISPLAY_NORMAL = 0; 103 private static final int CONTEXT_DISPLAY_SPLIT = 1; 104 105 private static final int INVALID_POSITION = -1; 106 107 private int mContextDisplayMode; 108 private boolean mHasEmbeddedTabs; 109 110 private int mCurWindowVisibility = View.VISIBLE; 111 112 private boolean mContentAnimations = true; 113 private boolean mHiddenByApp; 114 private boolean mHiddenBySystem; 115 private boolean mShowingForMode; 116 117 private boolean mNowShowing = true; 118 119 private Animator mCurrentShowAnim; 120 private boolean mShowHideAnimationEnabled; 121 boolean mHideOnContentScroll; 122 123 final AnimatorListener mHideListener = new AnimatorListenerAdapter() { 124 @Override 125 public void onAnimationEnd(Animator animation) { 126 if (mContentAnimations && mContentView != null) { 127 mContentView.setTranslationY(0); 128 mContainerView.setTranslationY(0); 129 } 130 if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { 131 mSplitView.setVisibility(View.GONE); 132 } 133 mContainerView.setVisibility(View.GONE); 134 mContainerView.setTransitioning(false); 135 mCurrentShowAnim = null; 136 completeDeferredDestroyActionMode(); 137 if (mOverlayLayout != null) { 138 mOverlayLayout.requestApplyInsets(); 139 } 140 } 141 }; 142 143 final AnimatorListener mShowListener = new AnimatorListenerAdapter() { 144 @Override 145 public void onAnimationEnd(Animator animation) { 146 mCurrentShowAnim = null; 147 mContainerView.requestLayout(); 148 } 149 }; 150 151 final ValueAnimator.AnimatorUpdateListener mUpdateListener = 152 new ValueAnimator.AnimatorUpdateListener() { 153 @Override 154 public void onAnimationUpdate(ValueAnimator animation) { 155 final ViewParent parent = mContainerView.getParent(); 156 ((View) parent).invalidate(); 157 } 158 }; 159 160 public WindowDecorActionBar(Activity activity) { 161 mActivity = activity; 162 Window window = activity.getWindow(); 163 View decor = window.getDecorView(); 164 boolean overlayMode = mActivity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY); 165 init(decor); 166 if (!overlayMode) { 167 mContentView = decor.findViewById(android.R.id.content); 168 } 169 } 170 171 public WindowDecorActionBar(Dialog dialog) { 172 mDialog = dialog; 173 init(dialog.getWindow().getDecorView()); 174 } 175 176 /** 177 * Only for edit mode. 178 * @hide 179 */ 180 public WindowDecorActionBar(View layout) { 181 assert layout.isInEditMode(); 182 init(layout); 183 } 184 185 private void init(View decor) { 186 mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById( 187 com.android.internal.R.id.decor_content_parent); 188 if (mOverlayLayout != null) { 189 mOverlayLayout.setActionBarVisibilityCallback(this); 190 } 191 mDecorToolbar = getDecorToolbar(decor.findViewById(com.android.internal.R.id.action_bar)); 192 mContextView = (ActionBarContextView) decor.findViewById( 193 com.android.internal.R.id.action_context_bar); 194 mContainerView = (ActionBarContainer) decor.findViewById( 195 com.android.internal.R.id.action_bar_container); 196 mSplitView = (ActionBarContainer) decor.findViewById( 197 com.android.internal.R.id.split_action_bar); 198 199 if (mDecorToolbar == null || mContextView == null || mContainerView == null) { 200 throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + 201 "with a compatible window decor layout"); 202 } 203 204 mContext = mDecorToolbar.getContext(); 205 mContextDisplayMode = mDecorToolbar.isSplit() ? 206 CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL; 207 208 // This was initially read from the action bar style 209 final int current = mDecorToolbar.getDisplayOptions(); 210 final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0; 211 if (homeAsUp) { 212 mDisplayHomeAsUpSet = true; 213 } 214 215 ActionBarPolicy abp = ActionBarPolicy.get(mContext); 216 setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp); 217 setHasEmbeddedTabs(abp.hasEmbeddedTabs()); 218 219 final TypedArray a = mContext.obtainStyledAttributes(null, 220 com.android.internal.R.styleable.ActionBar, 221 com.android.internal.R.attr.actionBarStyle, 0); 222 if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) { 223 setHideOnContentScrollEnabled(true); 224 } 225 final int elevation = a.getDimensionPixelSize(R.styleable.ActionBar_elevation, 0); 226 if (elevation != 0) { 227 setElevation(elevation); 228 } 229 a.recycle(); 230 } 231 232 private DecorToolbar getDecorToolbar(View view) { 233 if (view instanceof DecorToolbar) { 234 return (DecorToolbar) view; 235 } else if (view instanceof Toolbar) { 236 return ((Toolbar) view).getWrapper(); 237 } else { 238 throw new IllegalStateException("Can't make a decor toolbar out of " + 239 view.getClass().getSimpleName()); 240 } 241 } 242 243 @Override 244 public void setElevation(float elevation) { 245 mContainerView.setElevation(elevation); 246 if (mSplitView != null) { 247 mSplitView.setElevation(elevation); 248 } 249 } 250 251 @Override 252 public float getElevation() { 253 return mContainerView.getElevation(); 254 } 255 256 public void onConfigurationChanged(Configuration newConfig) { 257 setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs()); 258 } 259 260 private void setHasEmbeddedTabs(boolean hasEmbeddedTabs) { 261 mHasEmbeddedTabs = hasEmbeddedTabs; 262 // Switch tab layout configuration if needed 263 if (!mHasEmbeddedTabs) { 264 mDecorToolbar.setEmbeddedTabView(null); 265 mContainerView.setTabContainer(mTabScrollView); 266 } else { 267 mContainerView.setTabContainer(null); 268 mDecorToolbar.setEmbeddedTabView(mTabScrollView); 269 } 270 final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS; 271 if (mTabScrollView != null) { 272 if (isInTabMode) { 273 mTabScrollView.setVisibility(View.VISIBLE); 274 if (mOverlayLayout != null) { 275 mOverlayLayout.requestApplyInsets(); 276 } 277 } else { 278 mTabScrollView.setVisibility(View.GONE); 279 } 280 } 281 mDecorToolbar.setCollapsible(!mHasEmbeddedTabs && isInTabMode); 282 mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode); 283 } 284 285 private void ensureTabsExist() { 286 if (mTabScrollView != null) { 287 return; 288 } 289 290 ScrollingTabContainerView tabScroller = new ScrollingTabContainerView(mContext); 291 292 if (mHasEmbeddedTabs) { 293 tabScroller.setVisibility(View.VISIBLE); 294 mDecorToolbar.setEmbeddedTabView(tabScroller); 295 } else { 296 if (getNavigationMode() == NAVIGATION_MODE_TABS) { 297 tabScroller.setVisibility(View.VISIBLE); 298 if (mOverlayLayout != null) { 299 mOverlayLayout.requestApplyInsets(); 300 } 301 } else { 302 tabScroller.setVisibility(View.GONE); 303 } 304 mContainerView.setTabContainer(tabScroller); 305 } 306 mTabScrollView = tabScroller; 307 } 308 309 void completeDeferredDestroyActionMode() { 310 if (mDeferredModeDestroyCallback != null) { 311 mDeferredModeDestroyCallback.onDestroyActionMode(mDeferredDestroyActionMode); 312 mDeferredDestroyActionMode = null; 313 mDeferredModeDestroyCallback = null; 314 } 315 } 316 317 public void onWindowVisibilityChanged(int visibility) { 318 mCurWindowVisibility = visibility; 319 } 320 321 /** 322 * Enables or disables animation between show/hide states. 323 * If animation is disabled using this method, animations in progress 324 * will be finished. 325 * 326 * @param enabled true to animate, false to not animate. 327 */ 328 public void setShowHideAnimationEnabled(boolean enabled) { 329 mShowHideAnimationEnabled = enabled; 330 if (!enabled && mCurrentShowAnim != null) { 331 mCurrentShowAnim.end(); 332 } 333 } 334 335 public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) { 336 mMenuVisibilityListeners.add(listener); 337 } 338 339 public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) { 340 mMenuVisibilityListeners.remove(listener); 341 } 342 343 public void dispatchMenuVisibilityChanged(boolean isVisible) { 344 if (isVisible == mLastMenuVisibility) { 345 return; 346 } 347 mLastMenuVisibility = isVisible; 348 349 final int count = mMenuVisibilityListeners.size(); 350 for (int i = 0; i < count; i++) { 351 mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible); 352 } 353 } 354 355 @Override 356 public void setCustomView(int resId) { 357 setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, 358 mDecorToolbar.getViewGroup(), false)); 359 } 360 361 @Override 362 public void setDisplayUseLogoEnabled(boolean useLogo) { 363 setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO); 364 } 365 366 @Override 367 public void setDisplayShowHomeEnabled(boolean showHome) { 368 setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME); 369 } 370 371 @Override 372 public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) { 373 setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP); 374 } 375 376 @Override 377 public void setDisplayShowTitleEnabled(boolean showTitle) { 378 setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE); 379 } 380 381 @Override 382 public void setDisplayShowCustomEnabled(boolean showCustom) { 383 setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM); 384 } 385 386 @Override 387 public void setHomeButtonEnabled(boolean enable) { 388 mDecorToolbar.setHomeButtonEnabled(enable); 389 } 390 391 @Override 392 public void setTitle(int resId) { 393 setTitle(mContext.getString(resId)); 394 } 395 396 @Override 397 public void setSubtitle(int resId) { 398 setSubtitle(mContext.getString(resId)); 399 } 400 401 public void setSelectedNavigationItem(int position) { 402 switch (mDecorToolbar.getNavigationMode()) { 403 case NAVIGATION_MODE_TABS: 404 selectTab(mTabs.get(position)); 405 break; 406 case NAVIGATION_MODE_LIST: 407 mDecorToolbar.setDropdownSelectedPosition(position); 408 break; 409 default: 410 throw new IllegalStateException( 411 "setSelectedNavigationIndex not valid for current navigation mode"); 412 } 413 } 414 415 public void removeAllTabs() { 416 cleanupTabs(); 417 } 418 419 private void cleanupTabs() { 420 if (mSelectedTab != null) { 421 selectTab(null); 422 } 423 mTabs.clear(); 424 if (mTabScrollView != null) { 425 mTabScrollView.removeAllTabs(); 426 } 427 mSavedTabPosition = INVALID_POSITION; 428 } 429 430 public void setTitle(CharSequence title) { 431 mDecorToolbar.setTitle(title); 432 } 433 434 @Override 435 public void setWindowTitle(CharSequence title) { 436 mDecorToolbar.setWindowTitle(title); 437 } 438 439 public void setSubtitle(CharSequence subtitle) { 440 mDecorToolbar.setSubtitle(subtitle); 441 } 442 443 public void setDisplayOptions(int options) { 444 if ((options & DISPLAY_HOME_AS_UP) != 0) { 445 mDisplayHomeAsUpSet = true; 446 } 447 mDecorToolbar.setDisplayOptions(options); 448 } 449 450 public void setDisplayOptions(int options, int mask) { 451 final int current = mDecorToolbar.getDisplayOptions(); 452 if ((mask & DISPLAY_HOME_AS_UP) != 0) { 453 mDisplayHomeAsUpSet = true; 454 } 455 mDecorToolbar.setDisplayOptions((options & mask) | (current & ~mask)); 456 } 457 458 public void setBackgroundDrawable(Drawable d) { 459 mContainerView.setPrimaryBackground(d); 460 } 461 462 public void setStackedBackgroundDrawable(Drawable d) { 463 mContainerView.setStackedBackground(d); 464 } 465 466 public void setSplitBackgroundDrawable(Drawable d) { 467 if (mSplitView != null) { 468 mSplitView.setSplitBackground(d); 469 } 470 } 471 472 public View getCustomView() { 473 return mDecorToolbar.getCustomView(); 474 } 475 476 public CharSequence getTitle() { 477 return mDecorToolbar.getTitle(); 478 } 479 480 public CharSequence getSubtitle() { 481 return mDecorToolbar.getSubtitle(); 482 } 483 484 public int getNavigationMode() { 485 return mDecorToolbar.getNavigationMode(); 486 } 487 488 public int getDisplayOptions() { 489 return mDecorToolbar.getDisplayOptions(); 490 } 491 492 public ActionMode startActionMode(ActionMode.Callback callback) { 493 if (mActionMode != null) { 494 mActionMode.finish(); 495 } 496 497 mOverlayLayout.setHideOnContentScrollEnabled(false); 498 mContextView.killMode(); 499 ActionModeImpl mode = new ActionModeImpl(mContextView.getContext(), callback); 500 if (mode.dispatchOnCreate()) { 501 mode.invalidate(); 502 mContextView.initForMode(mode); 503 animateToMode(true); 504 if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { 505 // TODO animate this 506 if (mSplitView.getVisibility() != View.VISIBLE) { 507 mSplitView.setVisibility(View.VISIBLE); 508 if (mOverlayLayout != null) { 509 mOverlayLayout.requestApplyInsets(); 510 } 511 } 512 } 513 mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 514 mActionMode = mode; 515 return mode; 516 } 517 return null; 518 } 519 520 private void configureTab(Tab tab, int position) { 521 final TabImpl tabi = (TabImpl) tab; 522 final ActionBar.TabListener callback = tabi.getCallback(); 523 524 if (callback == null) { 525 throw new IllegalStateException("Action Bar Tab must have a Callback"); 526 } 527 528 tabi.setPosition(position); 529 mTabs.add(position, tabi); 530 531 final int count = mTabs.size(); 532 for (int i = position + 1; i < count; i++) { 533 mTabs.get(i).setPosition(i); 534 } 535 } 536 537 @Override 538 public void addTab(Tab tab) { 539 addTab(tab, mTabs.isEmpty()); 540 } 541 542 @Override 543 public void addTab(Tab tab, int position) { 544 addTab(tab, position, mTabs.isEmpty()); 545 } 546 547 @Override 548 public void addTab(Tab tab, boolean setSelected) { 549 ensureTabsExist(); 550 mTabScrollView.addTab(tab, setSelected); 551 configureTab(tab, mTabs.size()); 552 if (setSelected) { 553 selectTab(tab); 554 } 555 } 556 557 @Override 558 public void addTab(Tab tab, int position, boolean setSelected) { 559 ensureTabsExist(); 560 mTabScrollView.addTab(tab, position, setSelected); 561 configureTab(tab, position); 562 if (setSelected) { 563 selectTab(tab); 564 } 565 } 566 567 @Override 568 public Tab newTab() { 569 return new TabImpl(); 570 } 571 572 @Override 573 public void removeTab(Tab tab) { 574 removeTabAt(tab.getPosition()); 575 } 576 577 @Override 578 public void removeTabAt(int position) { 579 if (mTabScrollView == null) { 580 // No tabs around to remove 581 return; 582 } 583 584 int selectedTabPosition = mSelectedTab != null 585 ? mSelectedTab.getPosition() : mSavedTabPosition; 586 mTabScrollView.removeTabAt(position); 587 TabImpl removedTab = mTabs.remove(position); 588 if (removedTab != null) { 589 removedTab.setPosition(-1); 590 } 591 592 final int newTabCount = mTabs.size(); 593 for (int i = position; i < newTabCount; i++) { 594 mTabs.get(i).setPosition(i); 595 } 596 597 if (selectedTabPosition == position) { 598 selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); 599 } 600 } 601 602 @Override 603 public void selectTab(Tab tab) { 604 if (getNavigationMode() != NAVIGATION_MODE_TABS) { 605 mSavedTabPosition = tab != null ? tab.getPosition() : INVALID_POSITION; 606 return; 607 } 608 609 final FragmentTransaction trans = mDecorToolbar.getViewGroup().isInEditMode() ? null : 610 mActivity.getFragmentManager().beginTransaction().disallowAddToBackStack(); 611 612 if (mSelectedTab == tab) { 613 if (mSelectedTab != null) { 614 mSelectedTab.getCallback().onTabReselected(mSelectedTab, trans); 615 mTabScrollView.animateToTab(tab.getPosition()); 616 } 617 } else { 618 mTabScrollView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION); 619 if (mSelectedTab != null) { 620 mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans); 621 } 622 mSelectedTab = (TabImpl) tab; 623 if (mSelectedTab != null) { 624 mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans); 625 } 626 } 627 628 if (trans != null && !trans.isEmpty()) { 629 trans.commit(); 630 } 631 } 632 633 @Override 634 public Tab getSelectedTab() { 635 return mSelectedTab; 636 } 637 638 @Override 639 public int getHeight() { 640 return mContainerView.getHeight(); 641 } 642 643 public void enableContentAnimations(boolean enabled) { 644 mContentAnimations = enabled; 645 } 646 647 @Override 648 public void show() { 649 if (mHiddenByApp) { 650 mHiddenByApp = false; 651 updateVisibility(false); 652 } 653 } 654 655 private void showForActionMode() { 656 if (!mShowingForMode) { 657 mShowingForMode = true; 658 if (mOverlayLayout != null) { 659 mOverlayLayout.setShowingForActionMode(true); 660 } 661 updateVisibility(false); 662 } 663 } 664 665 public void showForSystem() { 666 if (mHiddenBySystem) { 667 mHiddenBySystem = false; 668 updateVisibility(true); 669 } 670 } 671 672 @Override 673 public void hide() { 674 if (!mHiddenByApp) { 675 mHiddenByApp = true; 676 updateVisibility(false); 677 } 678 } 679 680 private void hideForActionMode() { 681 if (mShowingForMode) { 682 mShowingForMode = false; 683 if (mOverlayLayout != null) { 684 mOverlayLayout.setShowingForActionMode(false); 685 } 686 updateVisibility(false); 687 } 688 } 689 690 public void hideForSystem() { 691 if (!mHiddenBySystem) { 692 mHiddenBySystem = true; 693 updateVisibility(true); 694 } 695 } 696 697 @Override 698 public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { 699 if (hideOnContentScroll && !mOverlayLayout.isInOverlayMode()) { 700 throw new IllegalStateException("Action bar must be in overlay mode " + 701 "(Window.FEATURE_OVERLAY_ACTION_BAR) to enable hide on content scroll"); 702 } 703 mHideOnContentScroll = hideOnContentScroll; 704 mOverlayLayout.setHideOnContentScrollEnabled(hideOnContentScroll); 705 } 706 707 @Override 708 public boolean isHideOnContentScrollEnabled() { 709 return mOverlayLayout.isHideOnContentScrollEnabled(); 710 } 711 712 @Override 713 public int getHideOffset() { 714 return mOverlayLayout.getActionBarHideOffset(); 715 } 716 717 @Override 718 public void setHideOffset(int offset) { 719 if (offset != 0 && !mOverlayLayout.isInOverlayMode()) { 720 throw new IllegalStateException("Action bar must be in overlay mode " + 721 "(Window.FEATURE_OVERLAY_ACTION_BAR) to set a non-zero hide offset"); 722 } 723 mOverlayLayout.setActionBarHideOffset(offset); 724 } 725 726 private static boolean checkShowingFlags(boolean hiddenByApp, boolean hiddenBySystem, 727 boolean showingForMode) { 728 if (showingForMode) { 729 return true; 730 } else if (hiddenByApp || hiddenBySystem) { 731 return false; 732 } else { 733 return true; 734 } 735 } 736 737 private void updateVisibility(boolean fromSystem) { 738 // Based on the current state, should we be hidden or shown? 739 final boolean shown = checkShowingFlags(mHiddenByApp, mHiddenBySystem, 740 mShowingForMode); 741 742 if (shown) { 743 if (!mNowShowing) { 744 mNowShowing = true; 745 doShow(fromSystem); 746 } 747 } else { 748 if (mNowShowing) { 749 mNowShowing = false; 750 doHide(fromSystem); 751 } 752 } 753 } 754 755 public void doShow(boolean fromSystem) { 756 if (mCurrentShowAnim != null) { 757 mCurrentShowAnim.end(); 758 } 759 mContainerView.setVisibility(View.VISIBLE); 760 761 if (mCurWindowVisibility == View.VISIBLE && (mShowHideAnimationEnabled 762 || fromSystem)) { 763 mContainerView.setTranslationY(0); // because we're about to ask its window loc 764 float startingY = -mContainerView.getHeight(); 765 if (fromSystem) { 766 int topLeft[] = {0, 0}; 767 mContainerView.getLocationInWindow(topLeft); 768 startingY -= topLeft[1]; 769 } 770 mContainerView.setTranslationY(startingY); 771 AnimatorSet anim = new AnimatorSet(); 772 ObjectAnimator a = ObjectAnimator.ofFloat(mContainerView, View.TRANSLATION_Y, 0); 773 a.addUpdateListener(mUpdateListener); 774 AnimatorSet.Builder b = anim.play(a); 775 if (mContentAnimations && mContentView != null) { 776 b.with(ObjectAnimator.ofFloat(mContentView, View.TRANSLATION_Y, 777 startingY, 0)); 778 } 779 if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { 780 mSplitView.setTranslationY(mSplitView.getHeight()); 781 mSplitView.setVisibility(View.VISIBLE); 782 b.with(ObjectAnimator.ofFloat(mSplitView, View.TRANSLATION_Y, 0)); 783 } 784 anim.setInterpolator(AnimationUtils.loadInterpolator(mContext, 785 com.android.internal.R.interpolator.decelerate_cubic)); 786 anim.setDuration(250); 787 // If this is being shown from the system, add a small delay. 788 // This is because we will also be animating in the status bar, 789 // and these two elements can't be done in lock-step. So we give 790 // a little time for the status bar to start its animation before 791 // the action bar animates. (This corresponds to the corresponding 792 // case when hiding, where the status bar has a small delay before 793 // starting.) 794 anim.addListener(mShowListener); 795 mCurrentShowAnim = anim; 796 anim.start(); 797 } else { 798 mContainerView.setAlpha(1); 799 mContainerView.setTranslationY(0); 800 if (mContentAnimations && mContentView != null) { 801 mContentView.setTranslationY(0); 802 } 803 if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { 804 mSplitView.setAlpha(1); 805 mSplitView.setTranslationY(0); 806 mSplitView.setVisibility(View.VISIBLE); 807 } 808 mShowListener.onAnimationEnd(null); 809 } 810 if (mOverlayLayout != null) { 811 mOverlayLayout.requestApplyInsets(); 812 } 813 } 814 815 public void doHide(boolean fromSystem) { 816 if (mCurrentShowAnim != null) { 817 mCurrentShowAnim.end(); 818 } 819 820 if (mCurWindowVisibility == View.VISIBLE && (mShowHideAnimationEnabled 821 || fromSystem)) { 822 mContainerView.setAlpha(1); 823 mContainerView.setTransitioning(true); 824 AnimatorSet anim = new AnimatorSet(); 825 float endingY = -mContainerView.getHeight(); 826 if (fromSystem) { 827 int topLeft[] = {0, 0}; 828 mContainerView.getLocationInWindow(topLeft); 829 endingY -= topLeft[1]; 830 } 831 ObjectAnimator a = ObjectAnimator.ofFloat(mContainerView, View.TRANSLATION_Y, endingY); 832 a.addUpdateListener(mUpdateListener); 833 AnimatorSet.Builder b = anim.play(a); 834 if (mContentAnimations && mContentView != null) { 835 b.with(ObjectAnimator.ofFloat(mContentView, View.TRANSLATION_Y, 836 0, endingY)); 837 } 838 if (mSplitView != null && mSplitView.getVisibility() == View.VISIBLE) { 839 mSplitView.setAlpha(1); 840 b.with(ObjectAnimator.ofFloat(mSplitView, View.TRANSLATION_Y, 841 mSplitView.getHeight())); 842 } 843 anim.setInterpolator(AnimationUtils.loadInterpolator(mContext, 844 com.android.internal.R.interpolator.accelerate_cubic)); 845 anim.setDuration(250); 846 anim.addListener(mHideListener); 847 mCurrentShowAnim = anim; 848 anim.start(); 849 } else { 850 mHideListener.onAnimationEnd(null); 851 } 852 } 853 854 public boolean isShowing() { 855 final int height = getHeight(); 856 // Take into account the case where the bar has a 0 height due to not being measured yet. 857 return mNowShowing && (height == 0 || getHideOffset() < height); 858 } 859 860 void animateToMode(boolean toActionMode) { 861 if (toActionMode) { 862 showForActionMode(); 863 } else { 864 hideForActionMode(); 865 } 866 867 mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE); 868 mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE); 869 // mTabScrollView's visibility is not affected by action mode. 870 } 871 872 public Context getThemedContext() { 873 if (mThemedContext == null) { 874 TypedValue outValue = new TypedValue(); 875 Resources.Theme currentTheme = mContext.getTheme(); 876 currentTheme.resolveAttribute(com.android.internal.R.attr.actionBarWidgetTheme, 877 outValue, true); 878 final int targetThemeRes = outValue.resourceId; 879 880 if (targetThemeRes != 0 && mContext.getThemeResId() != targetThemeRes) { 881 mThemedContext = new ContextThemeWrapper(mContext, targetThemeRes); 882 } else { 883 mThemedContext = mContext; 884 } 885 } 886 return mThemedContext; 887 } 888 889 @Override 890 public boolean isTitleTruncated() { 891 return mDecorToolbar != null && mDecorToolbar.isTitleTruncated(); 892 } 893 894 @Override 895 public void setHomeAsUpIndicator(Drawable indicator) { 896 mDecorToolbar.setNavigationIcon(indicator); 897 } 898 899 @Override 900 public void setHomeAsUpIndicator(int resId) { 901 mDecorToolbar.setNavigationIcon(resId); 902 } 903 904 @Override 905 public void setHomeActionContentDescription(CharSequence description) { 906 mDecorToolbar.setNavigationContentDescription(description); 907 } 908 909 @Override 910 public void setHomeActionContentDescription(int resId) { 911 mDecorToolbar.setNavigationContentDescription(resId); 912 } 913 914 @Override 915 public void onContentScrollStarted() { 916 if (mCurrentShowAnim != null) { 917 mCurrentShowAnim.cancel(); 918 mCurrentShowAnim = null; 919 } 920 } 921 922 @Override 923 public void onContentScrollStopped() { 924 } 925 926 @Override 927 public boolean collapseActionView() { 928 if (mDecorToolbar != null && mDecorToolbar.hasExpandedActionView()) { 929 mDecorToolbar.collapseActionView(); 930 return true; 931 } 932 return false; 933 } 934 935 /** 936 * @hide 937 */ 938 public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback { 939 private final Context mActionModeContext; 940 private final MenuBuilder mMenu; 941 942 private ActionMode.Callback mCallback; 943 private WeakReference<View> mCustomView; 944 945 public ActionModeImpl(Context context, ActionMode.Callback callback) { 946 mActionModeContext = context; 947 mCallback = callback; 948 mMenu = new MenuBuilder(context) 949 .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 950 mMenu.setCallback(this); 951 } 952 953 @Override 954 public MenuInflater getMenuInflater() { 955 return new MenuInflater(mActionModeContext); 956 } 957 958 @Override 959 public Menu getMenu() { 960 return mMenu; 961 } 962 963 @Override 964 public void finish() { 965 if (mActionMode != this) { 966 // Not the active action mode - no-op 967 return; 968 } 969 970 // If this change in state is going to cause the action bar 971 // to be hidden, defer the onDestroy callback until the animation 972 // is finished and associated relayout is about to happen. This lets 973 // apps better anticipate visibility and layout behavior. 974 if (!checkShowingFlags(mHiddenByApp, mHiddenBySystem, false)) { 975 // With the current state but the action bar hidden, our 976 // overall showing state is going to be false. 977 mDeferredDestroyActionMode = this; 978 mDeferredModeDestroyCallback = mCallback; 979 } else { 980 mCallback.onDestroyActionMode(this); 981 } 982 mCallback = null; 983 animateToMode(false); 984 985 // Clear out the context mode views after the animation finishes 986 mContextView.closeMode(); 987 mDecorToolbar.getViewGroup().sendAccessibilityEvent( 988 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 989 mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll); 990 991 mActionMode = null; 992 } 993 994 @Override 995 public void invalidate() { 996 mMenu.stopDispatchingItemsChanged(); 997 try { 998 mCallback.onPrepareActionMode(this, mMenu); 999 } finally { 1000 mMenu.startDispatchingItemsChanged(); 1001 } 1002 } 1003 1004 public boolean dispatchOnCreate() { 1005 mMenu.stopDispatchingItemsChanged(); 1006 try { 1007 return mCallback.onCreateActionMode(this, mMenu); 1008 } finally { 1009 mMenu.startDispatchingItemsChanged(); 1010 } 1011 } 1012 1013 @Override 1014 public void setCustomView(View view) { 1015 mContextView.setCustomView(view); 1016 mCustomView = new WeakReference<View>(view); 1017 } 1018 1019 @Override 1020 public void setSubtitle(CharSequence subtitle) { 1021 mContextView.setSubtitle(subtitle); 1022 } 1023 1024 @Override 1025 public void setTitle(CharSequence title) { 1026 mContextView.setTitle(title); 1027 } 1028 1029 @Override 1030 public void setTitle(int resId) { 1031 setTitle(mContext.getResources().getString(resId)); 1032 } 1033 1034 @Override 1035 public void setSubtitle(int resId) { 1036 setSubtitle(mContext.getResources().getString(resId)); 1037 } 1038 1039 @Override 1040 public CharSequence getTitle() { 1041 return mContextView.getTitle(); 1042 } 1043 1044 @Override 1045 public CharSequence getSubtitle() { 1046 return mContextView.getSubtitle(); 1047 } 1048 1049 @Override 1050 public void setTitleOptionalHint(boolean titleOptional) { 1051 super.setTitleOptionalHint(titleOptional); 1052 mContextView.setTitleOptional(titleOptional); 1053 } 1054 1055 @Override 1056 public boolean isTitleOptional() { 1057 return mContextView.isTitleOptional(); 1058 } 1059 1060 @Override 1061 public View getCustomView() { 1062 return mCustomView != null ? mCustomView.get() : null; 1063 } 1064 1065 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 1066 if (mCallback != null) { 1067 return mCallback.onActionItemClicked(this, item); 1068 } else { 1069 return false; 1070 } 1071 } 1072 1073 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1074 } 1075 1076 public boolean onSubMenuSelected(SubMenuBuilder subMenu) { 1077 if (mCallback == null) { 1078 return false; 1079 } 1080 1081 if (!subMenu.hasVisibleItems()) { 1082 return true; 1083 } 1084 1085 new MenuPopupHelper(getThemedContext(), subMenu).show(); 1086 return true; 1087 } 1088 1089 public void onCloseSubMenu(SubMenuBuilder menu) { 1090 } 1091 1092 public void onMenuModeChange(MenuBuilder menu) { 1093 if (mCallback == null) { 1094 return; 1095 } 1096 invalidate(); 1097 mContextView.showOverflowMenu(); 1098 } 1099 } 1100 1101 /** 1102 * @hide 1103 */ 1104 public class TabImpl extends ActionBar.Tab { 1105 private ActionBar.TabListener mCallback; 1106 private Object mTag; 1107 private Drawable mIcon; 1108 private CharSequence mText; 1109 private CharSequence mContentDesc; 1110 private int mPosition = -1; 1111 private View mCustomView; 1112 1113 @Override 1114 public Object getTag() { 1115 return mTag; 1116 } 1117 1118 @Override 1119 public Tab setTag(Object tag) { 1120 mTag = tag; 1121 return this; 1122 } 1123 1124 public ActionBar.TabListener getCallback() { 1125 return mCallback; 1126 } 1127 1128 @Override 1129 public Tab setTabListener(ActionBar.TabListener callback) { 1130 mCallback = callback; 1131 return this; 1132 } 1133 1134 @Override 1135 public View getCustomView() { 1136 return mCustomView; 1137 } 1138 1139 @Override 1140 public Tab setCustomView(View view) { 1141 mCustomView = view; 1142 if (mPosition >= 0) { 1143 mTabScrollView.updateTab(mPosition); 1144 } 1145 return this; 1146 } 1147 1148 @Override 1149 public Tab setCustomView(int layoutResId) { 1150 return setCustomView(LayoutInflater.from(getThemedContext()) 1151 .inflate(layoutResId, null)); 1152 } 1153 1154 @Override 1155 public Drawable getIcon() { 1156 return mIcon; 1157 } 1158 1159 @Override 1160 public int getPosition() { 1161 return mPosition; 1162 } 1163 1164 public void setPosition(int position) { 1165 mPosition = position; 1166 } 1167 1168 @Override 1169 public CharSequence getText() { 1170 return mText; 1171 } 1172 1173 @Override 1174 public Tab setIcon(Drawable icon) { 1175 mIcon = icon; 1176 if (mPosition >= 0) { 1177 mTabScrollView.updateTab(mPosition); 1178 } 1179 return this; 1180 } 1181 1182 @Override 1183 public Tab setIcon(int resId) { 1184 return setIcon(mContext.getDrawable(resId)); 1185 } 1186 1187 @Override 1188 public Tab setText(CharSequence text) { 1189 mText = text; 1190 if (mPosition >= 0) { 1191 mTabScrollView.updateTab(mPosition); 1192 } 1193 return this; 1194 } 1195 1196 @Override 1197 public Tab setText(int resId) { 1198 return setText(mContext.getResources().getText(resId)); 1199 } 1200 1201 @Override 1202 public void select() { 1203 selectTab(this); 1204 } 1205 1206 @Override 1207 public Tab setContentDescription(int resId) { 1208 return setContentDescription(mContext.getResources().getText(resId)); 1209 } 1210 1211 @Override 1212 public Tab setContentDescription(CharSequence contentDesc) { 1213 mContentDesc = contentDesc; 1214 if (mPosition >= 0) { 1215 mTabScrollView.updateTab(mPosition); 1216 } 1217 return this; 1218 } 1219 1220 @Override 1221 public CharSequence getContentDescription() { 1222 return mContentDesc; 1223 } 1224 } 1225 1226 @Override 1227 public void setCustomView(View view) { 1228 mDecorToolbar.setCustomView(view); 1229 } 1230 1231 @Override 1232 public void setCustomView(View view, LayoutParams layoutParams) { 1233 view.setLayoutParams(layoutParams); 1234 mDecorToolbar.setCustomView(view); 1235 } 1236 1237 @Override 1238 public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { 1239 mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback)); 1240 } 1241 1242 @Override 1243 public int getSelectedNavigationIndex() { 1244 switch (mDecorToolbar.getNavigationMode()) { 1245 case NAVIGATION_MODE_TABS: 1246 return mSelectedTab != null ? mSelectedTab.getPosition() : -1; 1247 case NAVIGATION_MODE_LIST: 1248 return mDecorToolbar.getDropdownSelectedPosition(); 1249 default: 1250 return -1; 1251 } 1252 } 1253 1254 @Override 1255 public int getNavigationItemCount() { 1256 switch (mDecorToolbar.getNavigationMode()) { 1257 case NAVIGATION_MODE_TABS: 1258 return mTabs.size(); 1259 case NAVIGATION_MODE_LIST: 1260 return mDecorToolbar.getDropdownItemCount(); 1261 default: 1262 return 0; 1263 } 1264 } 1265 1266 @Override 1267 public int getTabCount() { 1268 return mTabs.size(); 1269 } 1270 1271 @Override 1272 public void setNavigationMode(int mode) { 1273 final int oldMode = mDecorToolbar.getNavigationMode(); 1274 switch (oldMode) { 1275 case NAVIGATION_MODE_TABS: 1276 mSavedTabPosition = getSelectedNavigationIndex(); 1277 selectTab(null); 1278 mTabScrollView.setVisibility(View.GONE); 1279 break; 1280 } 1281 if (oldMode != mode && !mHasEmbeddedTabs) { 1282 if (mOverlayLayout != null) { 1283 mOverlayLayout.requestFitSystemWindows(); 1284 } 1285 } 1286 mDecorToolbar.setNavigationMode(mode); 1287 switch (mode) { 1288 case NAVIGATION_MODE_TABS: 1289 ensureTabsExist(); 1290 mTabScrollView.setVisibility(View.VISIBLE); 1291 if (mSavedTabPosition != INVALID_POSITION) { 1292 setSelectedNavigationItem(mSavedTabPosition); 1293 mSavedTabPosition = INVALID_POSITION; 1294 } 1295 break; 1296 } 1297 mDecorToolbar.setCollapsible(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); 1298 mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); 1299 } 1300 1301 @Override 1302 public Tab getTabAt(int index) { 1303 return mTabs.get(index); 1304 } 1305 1306 1307 @Override 1308 public void setIcon(int resId) { 1309 mDecorToolbar.setIcon(resId); 1310 } 1311 1312 @Override 1313 public void setIcon(Drawable icon) { 1314 mDecorToolbar.setIcon(icon); 1315 } 1316 1317 public boolean hasIcon() { 1318 return mDecorToolbar.hasIcon(); 1319 } 1320 1321 @Override 1322 public void setLogo(int resId) { 1323 mDecorToolbar.setLogo(resId); 1324 } 1325 1326 @Override 1327 public void setLogo(Drawable logo) { 1328 mDecorToolbar.setLogo(logo); 1329 } 1330 1331 public boolean hasLogo() { 1332 return mDecorToolbar.hasLogo(); 1333 } 1334 1335 public void setDefaultDisplayHomeAsUpEnabled(boolean enable) { 1336 if (!mDisplayHomeAsUpSet) { 1337 setDisplayHomeAsUpEnabled(enable); 1338 } 1339 } 1340 1341} 1342