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