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 android.support.v7.internal.app; 19 20import android.content.Context; 21import android.content.res.Configuration; 22import android.content.res.Resources; 23import android.graphics.drawable.Drawable; 24import android.support.annotation.Nullable; 25import android.support.v4.view.ViewCompat; 26import android.support.v4.view.WindowCompat; 27import android.support.v7.app.ActionBar; 28import android.support.v7.appcompat.R; 29import android.support.v7.internal.view.menu.ListMenuPresenter; 30import android.support.v7.internal.view.menu.MenuBuilder; 31import android.support.v7.internal.view.menu.MenuPresenter; 32import android.support.v7.internal.widget.DecorToolbar; 33import android.support.v7.internal.widget.ToolbarWidgetWrapper; 34import android.support.v7.view.ActionMode; 35import android.support.v7.widget.Toolbar; 36import android.support.v7.widget.WindowCallbackWrapper; 37import android.util.TypedValue; 38import android.view.ContextThemeWrapper; 39import android.view.KeyEvent; 40import android.view.LayoutInflater; 41import android.view.Menu; 42import android.view.MenuItem; 43import android.view.View; 44import android.view.Window; 45import android.widget.SpinnerAdapter; 46 47import java.util.ArrayList; 48 49/** 50 * @hide 51 */ 52public class ToolbarActionBar extends ActionBar { 53 private DecorToolbar mDecorToolbar; 54 private boolean mToolbarMenuPrepared; 55 private WindowCallback mWindowCallback; 56 private boolean mMenuCallbackSet; 57 58 private boolean mLastMenuVisibility; 59 private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners = 60 new ArrayList<OnMenuVisibilityListener>(); 61 62 private Window mWindow; 63 private ListMenuPresenter mListMenuPresenter; 64 65 private final Runnable mMenuInvalidator = new Runnable() { 66 @Override 67 public void run() { 68 populateOptionsMenu(); 69 } 70 }; 71 72 private final Toolbar.OnMenuItemClickListener mMenuClicker = 73 new Toolbar.OnMenuItemClickListener() { 74 @Override 75 public boolean onMenuItemClick(MenuItem item) { 76 return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); 77 } 78 }; 79 80 public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window window, 81 WindowCallback windowCallback) { 82 mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false); 83 mWindowCallback = new ToolbarCallbackWrapper(windowCallback); 84 mDecorToolbar.setWindowCallback(mWindowCallback); 85 toolbar.setOnMenuItemClickListener(mMenuClicker); 86 mDecorToolbar.setWindowTitle(title); 87 88 mWindow = window; 89 } 90 91 public WindowCallback getWrappedWindowCallback() { 92 return mWindowCallback; 93 } 94 95 @Override 96 public void setCustomView(View view) { 97 setCustomView(view, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 98 } 99 100 @Override 101 public void setCustomView(View view, LayoutParams layoutParams) { 102 view.setLayoutParams(layoutParams); 103 mDecorToolbar.setCustomView(view); 104 } 105 106 @Override 107 public void setCustomView(int resId) { 108 final LayoutInflater inflater = LayoutInflater.from(mDecorToolbar.getContext()); 109 setCustomView(inflater.inflate(resId, mDecorToolbar.getViewGroup(), false)); 110 } 111 112 @Override 113 public void setIcon(int resId) { 114 mDecorToolbar.setIcon(resId); 115 } 116 117 @Override 118 public void setIcon(Drawable icon) { 119 mDecorToolbar.setIcon(icon); 120 } 121 122 @Override 123 public void setLogo(int resId) { 124 mDecorToolbar.setLogo(resId); 125 } 126 127 @Override 128 public void setLogo(Drawable logo) { 129 mDecorToolbar.setLogo(logo); 130 } 131 132 @Override 133 public void setStackedBackgroundDrawable(Drawable d) { 134 // This space for rent (do nothing) 135 } 136 137 @Override 138 public void setSplitBackgroundDrawable(Drawable d) { 139 // This space for rent (do nothing) 140 } 141 142 @Override 143 public void setHomeButtonEnabled(boolean enabled) { 144 // If the nav button on a Toolbar is present, it's enabled. No-op. 145 } 146 147 @Override 148 public void setElevation(float elevation) { 149 ViewCompat.setElevation(mDecorToolbar.getViewGroup(), elevation); 150 } 151 152 @Override 153 public float getElevation() { 154 return ViewCompat.getElevation(mDecorToolbar.getViewGroup()); 155 } 156 157 @Override 158 public Context getThemedContext() { 159 return mDecorToolbar.getContext(); 160 } 161 162 @Override 163 public boolean isTitleTruncated() { 164 return super.isTitleTruncated(); 165 } 166 167 @Override 168 public void setHomeAsUpIndicator(Drawable indicator) { 169 mDecorToolbar.setNavigationIcon(indicator); 170 } 171 172 @Override 173 public void setHomeAsUpIndicator(int resId) { 174 mDecorToolbar.setNavigationIcon(resId); 175 } 176 177 @Override 178 public void setHomeActionContentDescription(CharSequence description) { 179 mDecorToolbar.setNavigationContentDescription(description); 180 } 181 182 @Override 183 public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) { 184 // Do nothing 185 } 186 187 @Override 188 public void setHomeActionContentDescription(int resId) { 189 mDecorToolbar.setNavigationContentDescription(resId); 190 } 191 192 @Override 193 public void setShowHideAnimationEnabled(boolean enabled) { 194 // This space for rent; no-op. 195 } 196 197 @Override 198 public void onConfigurationChanged(Configuration config) { 199 super.onConfigurationChanged(config); 200 } 201 202 @Override 203 public ActionMode startActionMode(ActionMode.Callback callback) { 204 return mWindowCallback.startActionMode(callback); 205 } 206 207 @Override 208 public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { 209 mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback)); 210 } 211 212 @Override 213 public void setSelectedNavigationItem(int position) { 214 switch (mDecorToolbar.getNavigationMode()) { 215 case NAVIGATION_MODE_LIST: 216 mDecorToolbar.setDropdownSelectedPosition(position); 217 break; 218 default: 219 throw new IllegalStateException( 220 "setSelectedNavigationIndex not valid for current navigation mode"); 221 } 222 } 223 224 @Override 225 public int getSelectedNavigationIndex() { 226 return -1; 227 } 228 229 @Override 230 public int getNavigationItemCount() { 231 return 0; 232 } 233 234 @Override 235 public void setTitle(CharSequence title) { 236 mDecorToolbar.setTitle(title); 237 } 238 239 @Override 240 public void setTitle(int resId) { 241 mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null); 242 } 243 244 @Override 245 public void setWindowTitle(CharSequence title) { 246 mDecorToolbar.setWindowTitle(title); 247 } 248 249 @Override 250 public void setSubtitle(CharSequence subtitle) { 251 mDecorToolbar.setSubtitle(subtitle); 252 } 253 254 @Override 255 public void setSubtitle(int resId) { 256 mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null); 257 } 258 259 @Override 260 public void setDisplayOptions(@DisplayOptions int options) { 261 setDisplayOptions(options, 0xffffffff); 262 } 263 264 @Override 265 public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) { 266 final int currentOptions = mDecorToolbar.getDisplayOptions(); 267 mDecorToolbar.setDisplayOptions(options & mask | currentOptions & ~mask); 268 } 269 270 @Override 271 public void setDisplayUseLogoEnabled(boolean useLogo) { 272 setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO); 273 } 274 275 @Override 276 public void setDisplayShowHomeEnabled(boolean showHome) { 277 setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME); 278 } 279 280 @Override 281 public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) { 282 setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP); 283 } 284 285 @Override 286 public void setDisplayShowTitleEnabled(boolean showTitle) { 287 setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE); 288 } 289 290 @Override 291 public void setDisplayShowCustomEnabled(boolean showCustom) { 292 setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM); 293 } 294 295 @Override 296 public void setBackgroundDrawable(@Nullable Drawable d) { 297 mDecorToolbar.setBackgroundDrawable(d); 298 } 299 300 @Override 301 public View getCustomView() { 302 return mDecorToolbar.getCustomView(); 303 } 304 305 @Override 306 public CharSequence getTitle() { 307 return mDecorToolbar.getTitle(); 308 } 309 310 @Override 311 public CharSequence getSubtitle() { 312 return mDecorToolbar.getSubtitle(); 313 } 314 315 @Override 316 public int getNavigationMode() { 317 return NAVIGATION_MODE_STANDARD; 318 } 319 320 @Override 321 public void setNavigationMode(@NavigationMode int mode) { 322 if (mode == ActionBar.NAVIGATION_MODE_TABS) { 323 throw new IllegalArgumentException("Tabs not supported in this configuration"); 324 } 325 mDecorToolbar.setNavigationMode(mode); 326 } 327 328 @Override 329 public int getDisplayOptions() { 330 return mDecorToolbar.getDisplayOptions(); 331 } 332 333 @Override 334 public Tab newTab() { 335 throw new UnsupportedOperationException( 336 "Tabs are not supported in toolbar action bars"); 337 } 338 339 @Override 340 public void addTab(Tab tab) { 341 throw new UnsupportedOperationException( 342 "Tabs are not supported in toolbar action bars"); 343 } 344 345 @Override 346 public void addTab(Tab tab, boolean setSelected) { 347 throw new UnsupportedOperationException( 348 "Tabs are not supported in toolbar action bars"); 349 } 350 351 @Override 352 public void addTab(Tab tab, int position) { 353 throw new UnsupportedOperationException( 354 "Tabs are not supported in toolbar action bars"); 355 } 356 357 @Override 358 public void addTab(Tab tab, int position, boolean setSelected) { 359 throw new UnsupportedOperationException( 360 "Tabs are not supported in toolbar action bars"); 361 } 362 363 @Override 364 public void removeTab(Tab tab) { 365 throw new UnsupportedOperationException( 366 "Tabs are not supported in toolbar action bars"); 367 } 368 369 @Override 370 public void removeTabAt(int position) { 371 throw new UnsupportedOperationException( 372 "Tabs are not supported in toolbar action bars"); 373 } 374 375 @Override 376 public void removeAllTabs() { 377 throw new UnsupportedOperationException( 378 "Tabs are not supported in toolbar action bars"); 379 } 380 381 @Override 382 public void selectTab(Tab tab) { 383 throw new UnsupportedOperationException( 384 "Tabs are not supported in toolbar action bars"); 385 } 386 387 @Override 388 public Tab getSelectedTab() { 389 throw new UnsupportedOperationException( 390 "Tabs are not supported in toolbar action bars"); 391 } 392 393 @Override 394 public Tab getTabAt(int index) { 395 throw new UnsupportedOperationException( 396 "Tabs are not supported in toolbar action bars"); 397 } 398 399 @Override 400 public int getTabCount() { 401 return 0; 402 } 403 404 @Override 405 public int getHeight() { 406 return mDecorToolbar.getHeight(); 407 } 408 409 @Override 410 public void show() { 411 // TODO: Consider a better transition for this. 412 // Right now use no automatic transition so that the app can supply one if desired. 413 mDecorToolbar.setVisibility(View.VISIBLE); 414 } 415 416 @Override 417 public void hide() { 418 // TODO: Consider a better transition for this. 419 // Right now use no automatic transition so that the app can supply one if desired. 420 mDecorToolbar.setVisibility(View.GONE); 421 } 422 423 @Override 424 public boolean isShowing() { 425 return mDecorToolbar.getVisibility() == View.VISIBLE; 426 } 427 428 @Override 429 public boolean openOptionsMenu() { 430 return mDecorToolbar.showOverflowMenu(); 431 } 432 433 @Override 434 public boolean invalidateOptionsMenu() { 435 mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator); 436 ViewCompat.postOnAnimation(mDecorToolbar.getViewGroup(), mMenuInvalidator); 437 return true; 438 } 439 440 @Override 441 public boolean collapseActionView() { 442 if (mDecorToolbar.hasExpandedActionView()) { 443 mDecorToolbar.collapseActionView(); 444 return true; 445 } 446 return false; 447 } 448 449 void populateOptionsMenu() { 450 final Menu menu = getMenu(); 451 final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null; 452 if (mb != null) { 453 mb.stopDispatchingItemsChanged(); 454 } 455 try { 456 menu.clear(); 457 if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) || 458 !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) { 459 menu.clear(); 460 } 461 } finally { 462 if (mb != null) { 463 mb.startDispatchingItemsChanged(); 464 } 465 } 466 } 467 468 @Override 469 public boolean onMenuKeyEvent(KeyEvent event) { 470 if (event.getAction() == KeyEvent.ACTION_UP) { 471 openOptionsMenu(); 472 } 473 return true; 474 } 475 476 @Override 477 public boolean onKeyShortcut(int keyCode, KeyEvent ev) { 478 Menu menu = getMenu(); 479 return menu != null ? menu.performShortcut(keyCode, ev, 0) : false; 480 } 481 482 public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) { 483 mMenuVisibilityListeners.add(listener); 484 } 485 486 public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) { 487 mMenuVisibilityListeners.remove(listener); 488 } 489 490 public void dispatchMenuVisibilityChanged(boolean isVisible) { 491 if (isVisible == mLastMenuVisibility) { 492 return; 493 } 494 mLastMenuVisibility = isVisible; 495 496 final int count = mMenuVisibilityListeners.size(); 497 for (int i = 0; i < count; i++) { 498 mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible); 499 } 500 } 501 502 private View getListMenuView(Menu menu) { 503 ensureListMenuPresenter(menu); 504 505 if (menu == null || mListMenuPresenter == null) { 506 return null; 507 } 508 509 if (mListMenuPresenter.getAdapter().getCount() > 0) { 510 return (View) mListMenuPresenter.getMenuView(mDecorToolbar.getViewGroup()); 511 } 512 return null; 513 } 514 515 private void ensureListMenuPresenter(Menu menu) { 516 if (mListMenuPresenter == null && (menu instanceof MenuBuilder)) { 517 MenuBuilder mb = (MenuBuilder) menu; 518 519 Context context = mDecorToolbar.getContext(); 520 final TypedValue outValue = new TypedValue(); 521 final Resources.Theme widgetTheme = context.getResources().newTheme(); 522 widgetTheme.setTo(context.getTheme()); 523 524 // Apply the panelMenuListTheme 525 widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true); 526 if (outValue.resourceId != 0) { 527 widgetTheme.applyStyle(outValue.resourceId, true); 528 } else { 529 widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true); 530 } 531 532 context = new ContextThemeWrapper(context, 0); 533 context.getTheme().setTo(widgetTheme); 534 535 // Finally create the list menu presenter 536 mListMenuPresenter = new ListMenuPresenter(context, R.layout.abc_list_menu_item_layout); 537 mListMenuPresenter.setCallback(new PanelMenuPresenterCallback()); 538 mb.addMenuPresenter(mListMenuPresenter); 539 } 540 } 541 542 private class ToolbarCallbackWrapper extends WindowCallbackWrapper { 543 public ToolbarCallbackWrapper(WindowCallback wrapped) { 544 super(wrapped); 545 } 546 547 @Override 548 public boolean onPreparePanel(int featureId, View view, Menu menu) { 549 final boolean result = super.onPreparePanel(featureId, view, menu); 550 if (result && !mToolbarMenuPrepared) { 551 mDecorToolbar.setMenuPrepared(); 552 mToolbarMenuPrepared = true; 553 } 554 return result; 555 } 556 557 @Override 558 public View onCreatePanelView(int featureId) { 559 switch (featureId) { 560 case Window.FEATURE_OPTIONS_PANEL: 561 final Menu menu = mDecorToolbar.getMenu(); 562 if (mWindowCallback != null && 563 mWindowCallback.onPreparePanel(featureId, null, menu) && 564 mWindowCallback.onMenuOpened(featureId, menu)) { 565 return getListMenuView(menu); 566 } 567 break; 568 } 569 return super.onCreatePanelView(featureId); 570 } 571 } 572 573 private Menu getMenu() { 574 if (!mMenuCallbackSet) { 575 mDecorToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(), 576 new MenuBuilderCallback()); 577 mMenuCallbackSet = true; 578 } 579 return mDecorToolbar.getMenu(); 580 } 581 582 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { 583 private boolean mClosingActionMenu; 584 585 @Override 586 public boolean onOpenSubMenu(MenuBuilder subMenu) { 587 if (mWindowCallback != null) { 588 mWindowCallback.onMenuOpened(WindowCompat.FEATURE_ACTION_BAR, subMenu); 589 return true; 590 } 591 return false; 592 } 593 594 @Override 595 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 596 if (mClosingActionMenu) { 597 return; 598 } 599 600 mClosingActionMenu = true; 601 mDecorToolbar.dismissPopupMenus(); 602 if (mWindowCallback != null) { 603 mWindowCallback.onPanelClosed(WindowCompat.FEATURE_ACTION_BAR, menu); 604 } 605 mClosingActionMenu = false; 606 } 607 } 608 609 private final class PanelMenuPresenterCallback implements MenuPresenter.Callback { 610 @Override 611 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 612 if (mWindowCallback != null) { 613 mWindowCallback.onPanelClosed(Window.FEATURE_OPTIONS_PANEL, menu); 614 } 615 616 // Close the options panel 617 mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL); 618 } 619 620 @Override 621 public boolean onOpenSubMenu(MenuBuilder subMenu) { 622 if (subMenu == null && mWindowCallback != null) { 623 mWindowCallback.onMenuOpened(Window.FEATURE_OPTIONS_PANEL, subMenu); 624 } 625 return true; 626 } 627 } 628 629 private final class MenuBuilderCallback implements MenuBuilder.Callback { 630 631 @Override 632 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 633 return false; 634 } 635 636 @Override 637 public void onMenuModeChange(MenuBuilder menu) { 638 if (mWindowCallback != null) { 639 if (mDecorToolbar.isOverflowMenuShowing()) { 640 mWindowCallback.onPanelClosed(WindowCompat.FEATURE_ACTION_BAR, menu); 641 } else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, 642 null, menu)) { 643 mWindowCallback.onMenuOpened(WindowCompat.FEATURE_ACTION_BAR, menu); 644 } 645 } 646 } 647 } 648} 649