1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18package com.android.internal.widget; 19 20import android.animation.Animator; 21import android.animation.AnimatorListenerAdapter; 22import android.app.ActionBar; 23import android.content.Context; 24import android.content.res.TypedArray; 25import android.graphics.drawable.Drawable; 26import android.os.Parcelable; 27import android.text.TextUtils; 28import android.util.Log; 29import android.util.SparseArray; 30import android.util.TypedValue; 31import android.view.ContextThemeWrapper; 32import android.view.Gravity; 33import android.view.LayoutInflater; 34import android.view.Menu; 35import android.view.View; 36import android.view.ViewGroup; 37import android.view.Window; 38import android.widget.ActionMenuPresenter; 39import android.widget.AdapterView; 40import android.widget.Spinner; 41import android.widget.SpinnerAdapter; 42import android.widget.Toolbar; 43import com.android.internal.R; 44import com.android.internal.view.menu.ActionMenuItem; 45import com.android.internal.view.menu.MenuBuilder; 46import com.android.internal.view.menu.MenuPresenter; 47 48/** 49 * Internal class used to interact with the Toolbar widget without 50 * exposing interface methods to the public API. 51 * 52 * <p>ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView 53 * so that either variant acting as a 54 * {@link com.android.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave 55 * in the same way.</p> 56 * 57 * @hide 58 */ 59public class ToolbarWidgetWrapper implements DecorToolbar { 60 private static final String TAG = "ToolbarWidgetWrapper"; 61 62 private static final int AFFECTS_LOGO_MASK = 63 ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO; 64 65 private Toolbar mToolbar; 66 67 private int mDisplayOpts; 68 private View mTabView; 69 private Spinner mSpinner; 70 private View mCustomView; 71 72 private Drawable mIcon; 73 private Drawable mLogo; 74 private Drawable mNavIcon; 75 76 private boolean mTitleSet; 77 private CharSequence mTitle; 78 private CharSequence mSubtitle; 79 private CharSequence mHomeDescription; 80 81 private Window.Callback mWindowCallback; 82 private boolean mMenuPrepared; 83 private ActionMenuPresenter mActionMenuPresenter; 84 85 private int mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD; 86 private int mDefaultNavigationContentDescription = 0; 87 private Drawable mDefaultNavigationIcon; 88 89 public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) { 90 this(toolbar, style, R.string.action_bar_up_description); 91 } 92 93 public ToolbarWidgetWrapper(Toolbar toolbar, boolean style, 94 int defaultNavigationContentDescription) { 95 mToolbar = toolbar; 96 97 mTitle = toolbar.getTitle(); 98 mSubtitle = toolbar.getSubtitle(); 99 mTitleSet = mTitle != null; 100 mNavIcon = mToolbar.getNavigationIcon(); 101 final TypedArray a = toolbar.getContext().obtainStyledAttributes(null, 102 R.styleable.ActionBar, R.attr.actionBarStyle, 0); 103 mDefaultNavigationIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator); 104 if (style) { 105 final CharSequence title = a.getText(R.styleable.ActionBar_title); 106 if (!TextUtils.isEmpty(title)) { 107 setTitle(title); 108 } 109 110 final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle); 111 if (!TextUtils.isEmpty(subtitle)) { 112 setSubtitle(subtitle); 113 } 114 115 final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo); 116 if (logo != null) { 117 setLogo(logo); 118 } 119 120 final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon); 121 if (icon != null) { 122 setIcon(icon); 123 } 124 if (mNavIcon == null && mDefaultNavigationIcon != null) { 125 setNavigationIcon(mDefaultNavigationIcon); 126 } 127 setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0)); 128 129 final int customNavId = a.getResourceId( 130 R.styleable.ActionBar_customNavigationLayout, 0); 131 if (customNavId != 0) { 132 setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId, 133 mToolbar, false)); 134 setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM); 135 } 136 137 final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0); 138 if (height > 0) { 139 final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams(); 140 lp.height = height; 141 mToolbar.setLayoutParams(lp); 142 } 143 144 final int contentInsetStart = a.getDimensionPixelOffset( 145 R.styleable.ActionBar_contentInsetStart, -1); 146 final int contentInsetEnd = a.getDimensionPixelOffset( 147 R.styleable.ActionBar_contentInsetEnd, -1); 148 if (contentInsetStart >= 0 || contentInsetEnd >= 0) { 149 mToolbar.setContentInsetsRelative(Math.max(contentInsetStart, 0), 150 Math.max(contentInsetEnd, 0)); 151 } 152 153 final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0); 154 if (titleTextStyle != 0) { 155 mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle); 156 } 157 158 final int subtitleTextStyle = a.getResourceId( 159 R.styleable.ActionBar_subtitleTextStyle, 0); 160 if (subtitleTextStyle != 0) { 161 mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle); 162 } 163 164 final int popupTheme = a.getResourceId(R.styleable.ActionBar_popupTheme, 0); 165 if (popupTheme != 0) { 166 mToolbar.setPopupTheme(popupTheme); 167 } 168 } else { 169 mDisplayOpts = detectDisplayOptions(); 170 } 171 a.recycle(); 172 173 setDefaultNavigationContentDescription(defaultNavigationContentDescription); 174 mHomeDescription = mToolbar.getNavigationContentDescription(); 175 176 mToolbar.setNavigationOnClickListener(new View.OnClickListener() { 177 final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(), 178 0, android.R.id.home, 0, 0, mTitle); 179 @Override 180 public void onClick(View v) { 181 if (mWindowCallback != null && mMenuPrepared) { 182 mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem); 183 } 184 } 185 }); 186 } 187 188 @Override 189 public void setDefaultNavigationContentDescription(int defaultNavigationContentDescription) { 190 if (defaultNavigationContentDescription == mDefaultNavigationContentDescription) { 191 return; 192 } 193 mDefaultNavigationContentDescription = defaultNavigationContentDescription; 194 if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) { 195 setNavigationContentDescription(mDefaultNavigationContentDescription); 196 } 197 } 198 199 private int detectDisplayOptions() { 200 int opts = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME | 201 ActionBar.DISPLAY_USE_LOGO; 202 if (mToolbar.getNavigationIcon() != null) { 203 opts |= ActionBar.DISPLAY_HOME_AS_UP; 204 mDefaultNavigationIcon = mToolbar.getNavigationIcon(); 205 } 206 return opts; 207 } 208 209 @Override 210 public ViewGroup getViewGroup() { 211 return mToolbar; 212 } 213 214 @Override 215 public Context getContext() { 216 return mToolbar.getContext(); 217 } 218 219 @Override 220 public boolean isSplit() { 221 return false; 222 } 223 224 @Override 225 public boolean hasExpandedActionView() { 226 return mToolbar.hasExpandedActionView(); 227 } 228 229 @Override 230 public void collapseActionView() { 231 mToolbar.collapseActionView(); 232 } 233 234 @Override 235 public void setWindowCallback(Window.Callback cb) { 236 mWindowCallback = cb; 237 } 238 239 @Override 240 public void setWindowTitle(CharSequence title) { 241 // "Real" title always trumps window title. 242 if (!mTitleSet) { 243 setTitleInt(title); 244 } 245 } 246 247 @Override 248 public CharSequence getTitle() { 249 return mToolbar.getTitle(); 250 } 251 252 @Override 253 public void setTitle(CharSequence title) { 254 mTitleSet = true; 255 setTitleInt(title); 256 } 257 258 private void setTitleInt(CharSequence title) { 259 mTitle = title; 260 if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { 261 mToolbar.setTitle(title); 262 } 263 } 264 265 @Override 266 public CharSequence getSubtitle() { 267 return mToolbar.getSubtitle(); 268 } 269 270 @Override 271 public void setSubtitle(CharSequence subtitle) { 272 mSubtitle = subtitle; 273 if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { 274 mToolbar.setSubtitle(subtitle); 275 } 276 } 277 278 @Override 279 public void initProgress() { 280 Log.i(TAG, "Progress display unsupported"); 281 } 282 283 @Override 284 public void initIndeterminateProgress() { 285 Log.i(TAG, "Progress display unsupported"); 286 } 287 288 @Override 289 public boolean canSplit() { 290 return false; 291 } 292 293 @Override 294 public void setSplitView(ViewGroup splitView) { 295 } 296 297 @Override 298 public void setSplitToolbar(boolean split) { 299 if (split) { 300 throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar"); 301 } 302 } 303 304 @Override 305 public void setSplitWhenNarrow(boolean splitWhenNarrow) { 306 // Ignore. 307 } 308 309 @Override 310 public boolean hasIcon() { 311 return mIcon != null; 312 } 313 314 @Override 315 public boolean hasLogo() { 316 return mLogo != null; 317 } 318 319 @Override 320 public void setIcon(int resId) { 321 setIcon(resId != 0 ? getContext().getDrawable(resId) : null); 322 } 323 324 @Override 325 public void setIcon(Drawable d) { 326 mIcon = d; 327 updateToolbarLogo(); 328 } 329 330 @Override 331 public void setLogo(int resId) { 332 setLogo(resId != 0 ? getContext().getDrawable(resId) : null); 333 } 334 335 @Override 336 public void setLogo(Drawable d) { 337 mLogo = d; 338 updateToolbarLogo(); 339 } 340 341 private void updateToolbarLogo() { 342 Drawable logo = null; 343 if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) { 344 if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) { 345 logo = mLogo != null ? mLogo : mIcon; 346 } else { 347 logo = mIcon; 348 } 349 } 350 mToolbar.setLogo(logo); 351 } 352 353 @Override 354 public boolean canShowOverflowMenu() { 355 return mToolbar.canShowOverflowMenu(); 356 } 357 358 @Override 359 public boolean isOverflowMenuShowing() { 360 return mToolbar.isOverflowMenuShowing(); 361 } 362 363 @Override 364 public boolean isOverflowMenuShowPending() { 365 return mToolbar.isOverflowMenuShowPending(); 366 } 367 368 @Override 369 public boolean showOverflowMenu() { 370 return mToolbar.showOverflowMenu(); 371 } 372 373 @Override 374 public boolean hideOverflowMenu() { 375 return mToolbar.hideOverflowMenu(); 376 } 377 378 @Override 379 public void setMenuPrepared() { 380 mMenuPrepared = true; 381 } 382 383 @Override 384 public void setMenu(Menu menu, MenuPresenter.Callback cb) { 385 if (mActionMenuPresenter == null) { 386 mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext()); 387 mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter); 388 } 389 mActionMenuPresenter.setCallback(cb); 390 mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter); 391 } 392 393 @Override 394 public void dismissPopupMenus() { 395 mToolbar.dismissPopupMenus(); 396 } 397 398 @Override 399 public int getDisplayOptions() { 400 return mDisplayOpts; 401 } 402 403 @Override 404 public void setDisplayOptions(int newOpts) { 405 final int oldOpts = mDisplayOpts; 406 final int changed = oldOpts ^ newOpts; 407 mDisplayOpts = newOpts; 408 if (changed != 0) { 409 if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) { 410 if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { 411 updateHomeAccessibility(); 412 } 413 updateNavigationIcon(); 414 } 415 416 if ((changed & AFFECTS_LOGO_MASK) != 0) { 417 updateToolbarLogo(); 418 } 419 420 if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) { 421 if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { 422 mToolbar.setTitle(mTitle); 423 mToolbar.setSubtitle(mSubtitle); 424 } else { 425 mToolbar.setTitle(null); 426 mToolbar.setSubtitle(null); 427 } 428 } 429 430 if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) { 431 if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { 432 mToolbar.addView(mCustomView); 433 } else { 434 mToolbar.removeView(mCustomView); 435 } 436 } 437 } 438 } 439 440 @Override 441 public void setEmbeddedTabView(ScrollingTabContainerView tabView) { 442 if (mTabView != null && mTabView.getParent() == mToolbar) { 443 mToolbar.removeView(mTabView); 444 } 445 mTabView = tabView; 446 if (tabView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) { 447 mToolbar.addView(mTabView, 0); 448 Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams(); 449 lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; 450 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; 451 lp.gravity = Gravity.START | Gravity.BOTTOM; 452 tabView.setAllowCollapse(true); 453 } 454 } 455 456 @Override 457 public boolean hasEmbeddedTabs() { 458 return mTabView != null; 459 } 460 461 @Override 462 public boolean isTitleTruncated() { 463 return mToolbar.isTitleTruncated(); 464 } 465 466 @Override 467 public void setCollapsible(boolean collapsible) { 468 mToolbar.setCollapsible(collapsible); 469 } 470 471 @Override 472 public void setHomeButtonEnabled(boolean enable) { 473 // Ignore 474 } 475 476 @Override 477 public int getNavigationMode() { 478 return mNavigationMode; 479 } 480 481 @Override 482 public void setNavigationMode(int mode) { 483 final int oldMode = mNavigationMode; 484 if (mode != oldMode) { 485 switch (oldMode) { 486 case ActionBar.NAVIGATION_MODE_LIST: 487 if (mSpinner != null && mSpinner.getParent() == mToolbar) { 488 mToolbar.removeView(mSpinner); 489 } 490 break; 491 case ActionBar.NAVIGATION_MODE_TABS: 492 if (mTabView != null && mTabView.getParent() == mToolbar) { 493 mToolbar.removeView(mTabView); 494 } 495 break; 496 } 497 498 mNavigationMode = mode; 499 500 switch (mode) { 501 case ActionBar.NAVIGATION_MODE_STANDARD: 502 break; 503 case ActionBar.NAVIGATION_MODE_LIST: 504 ensureSpinner(); 505 mToolbar.addView(mSpinner, 0); 506 break; 507 case ActionBar.NAVIGATION_MODE_TABS: 508 if (mTabView != null) { 509 mToolbar.addView(mTabView, 0); 510 Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams(); 511 lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; 512 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; 513 lp.gravity = Gravity.START | Gravity.BOTTOM; 514 } 515 break; 516 default: 517 throw new IllegalArgumentException("Invalid navigation mode " + mode); 518 } 519 } 520 } 521 522 private void ensureSpinner() { 523 if (mSpinner == null) { 524 mSpinner = new Spinner(getContext(), null, R.attr.actionDropDownStyle); 525 Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 526 ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL); 527 mSpinner.setLayoutParams(lp); 528 } 529 } 530 531 @Override 532 public void setDropdownParams(SpinnerAdapter adapter, 533 AdapterView.OnItemSelectedListener listener) { 534 ensureSpinner(); 535 mSpinner.setAdapter(adapter); 536 mSpinner.setOnItemSelectedListener(listener); 537 } 538 539 @Override 540 public void setDropdownSelectedPosition(int position) { 541 if (mSpinner == null) { 542 throw new IllegalStateException( 543 "Can't set dropdown selected position without an adapter"); 544 } 545 mSpinner.setSelection(position); 546 } 547 548 @Override 549 public int getDropdownSelectedPosition() { 550 return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0; 551 } 552 553 @Override 554 public int getDropdownItemCount() { 555 return mSpinner != null ? mSpinner.getCount() : 0; 556 } 557 558 @Override 559 public void setCustomView(View view) { 560 if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { 561 mToolbar.removeView(mCustomView); 562 } 563 mCustomView = view; 564 if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { 565 mToolbar.addView(mCustomView); 566 } 567 } 568 569 @Override 570 public View getCustomView() { 571 return mCustomView; 572 } 573 574 @Override 575 public void animateToVisibility(int visibility) { 576 if (visibility == View.GONE) { 577 mToolbar.animate().alpha(0) 578 .setListener(new AnimatorListenerAdapter() { 579 private boolean mCanceled = false; 580 @Override 581 public void onAnimationEnd(Animator animation) { 582 if (!mCanceled) { 583 mToolbar.setVisibility(View.GONE); 584 } 585 } 586 587 @Override 588 public void onAnimationCancel(Animator animation) { 589 mCanceled = true; 590 } 591 }); 592 } else if (visibility == View.VISIBLE) { 593 mToolbar.animate().alpha(1) 594 .setListener(new AnimatorListenerAdapter() { 595 @Override 596 public void onAnimationStart(Animator animation) { 597 mToolbar.setVisibility(View.VISIBLE); 598 } 599 }); 600 } 601 } 602 603 @Override 604 public void setNavigationIcon(Drawable icon) { 605 mNavIcon = icon; 606 updateNavigationIcon(); 607 } 608 609 @Override 610 public void setNavigationIcon(int resId) { 611 setNavigationIcon(resId != 0 ? mToolbar.getContext().getDrawable(resId) : null); 612 } 613 614 @Override 615 public void setDefaultNavigationIcon(Drawable defaultNavigationIcon) { 616 if (mDefaultNavigationIcon != defaultNavigationIcon) { 617 mDefaultNavigationIcon = defaultNavigationIcon; 618 updateNavigationIcon(); 619 } 620 } 621 622 private void updateNavigationIcon() { 623 if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { 624 mToolbar.setNavigationIcon(mNavIcon != null ? mNavIcon : mDefaultNavigationIcon); 625 } else { 626 mToolbar.setNavigationIcon(null); 627 } 628 } 629 630 @Override 631 public void setNavigationContentDescription(CharSequence description) { 632 mHomeDescription = description; 633 updateHomeAccessibility(); 634 } 635 636 @Override 637 public void setNavigationContentDescription(int resId) { 638 setNavigationContentDescription(resId == 0 ? null : getContext().getString(resId)); 639 } 640 641 private void updateHomeAccessibility() { 642 if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { 643 if (TextUtils.isEmpty(mHomeDescription)) { 644 mToolbar.setNavigationContentDescription(mDefaultNavigationContentDescription); 645 } else { 646 mToolbar.setNavigationContentDescription(mHomeDescription); 647 } 648 } 649 } 650 651 @Override 652 public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) { 653 mToolbar.saveHierarchyState(toolbarStates); 654 } 655 656 @Override 657 public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) { 658 mToolbar.restoreHierarchyState(toolbarStates); 659 } 660 661 @Override 662 public void setBackgroundDrawable(Drawable d) { 663 //noinspection deprecation 664 mToolbar.setBackgroundDrawable(d); 665 } 666 667 @Override 668 public int getHeight() { 669 return mToolbar.getHeight(); 670 } 671 672 @Override 673 public void setVisibility(int visible) { 674 mToolbar.setVisibility(visible); 675 } 676 677 @Override 678 public int getVisibility() { 679 return mToolbar.getVisibility(); 680 } 681 682 @Override 683 public void setMenuCallbacks(MenuPresenter.Callback presenterCallback, 684 MenuBuilder.Callback menuBuilderCallback) { 685 mToolbar.setMenuCallbacks(presenterCallback, menuBuilderCallback); 686 } 687 688 @Override 689 public Menu getMenu() { 690 return mToolbar.getMenu(); 691 } 692 693} 694