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