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