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