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