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