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