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