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