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