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 android.annotation.Nullable; 21 import android.app.ActionBar; 22 import android.content.Context; 23 import android.content.res.Configuration; 24 import android.graphics.drawable.Drawable; 25 import android.view.ActionMode; 26 import android.view.KeyCharacterMap; 27 import android.view.KeyEvent; 28 import android.view.LayoutInflater; 29 import android.view.Menu; 30 import android.view.MenuItem; 31 import android.view.View; 32 import android.view.View.OnFocusChangeListener; 33 import android.view.ViewGroup; 34 import android.view.ViewParent; 35 import android.view.Window; 36 import android.view.WindowCallbackWrapper; 37 import android.widget.SpinnerAdapter; 38 import android.widget.Toolbar; 39 40 import com.android.internal.view.menu.MenuBuilder; 41 import com.android.internal.view.menu.MenuPresenter; 42 import com.android.internal.widget.DecorToolbar; 43 import com.android.internal.widget.ToolbarWidgetWrapper; 44 45 import java.util.ArrayList; 46 47 public class ToolbarActionBar extends ActionBar { 48 private DecorToolbar mDecorToolbar; 49 private boolean mToolbarMenuPrepared; 50 private Window.Callback mWindowCallback; 51 private boolean mMenuCallbackSet; 52 53 private boolean mLastMenuVisibility; 54 private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners = 55 new ArrayList<OnMenuVisibilityListener>(); 56 57 private final Runnable mMenuInvalidator = new Runnable() { 58 @Override 59 public void run() { 60 populateOptionsMenu(); 61 } 62 }; 63 64 private final Toolbar.OnMenuItemClickListener mMenuClicker = 65 new Toolbar.OnMenuItemClickListener() { 66 @Override 67 public boolean onMenuItemClick(MenuItem item) { 68 return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); 69 } 70 }; 71 72 public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) { 73 mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false); 74 mWindowCallback = new ToolbarCallbackWrapper(windowCallback); 75 mDecorToolbar.setWindowCallback(mWindowCallback); 76 toolbar.setOnMenuItemClickListener(mMenuClicker); 77 mDecorToolbar.setWindowTitle(title); 78 } 79 80 public Window.Callback getWrappedWindowCallback() { 81 return mWindowCallback; 82 } 83 84 @Override 85 public void setCustomView(View view) { 86 setCustomView(view, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 87 } 88 89 @Override 90 public void setCustomView(View view, LayoutParams layoutParams) { 91 if (view != null) { 92 view.setLayoutParams(layoutParams); 93 } 94 mDecorToolbar.setCustomView(view); 95 } 96 97 @Override 98 public void setCustomView(int resId) { 99 final LayoutInflater inflater = LayoutInflater.from(mDecorToolbar.getContext()); 100 setCustomView(inflater.inflate(resId, mDecorToolbar.getViewGroup(), false)); 101 } 102 103 @Override 104 public void setIcon(int resId) { 105 mDecorToolbar.setIcon(resId); 106 } 107 108 @Override 109 public void setIcon(Drawable icon) { 110 mDecorToolbar.setIcon(icon); 111 } 112 113 @Override 114 public void setLogo(int resId) { 115 mDecorToolbar.setLogo(resId); 116 } 117 118 @Override 119 public void setLogo(Drawable logo) { 120 mDecorToolbar.setLogo(logo); 121 } 122 123 @Override 124 public void setStackedBackgroundDrawable(Drawable d) { 125 // This space for rent (do nothing) 126 } 127 128 @Override 129 public void setSplitBackgroundDrawable(Drawable d) { 130 // This space for rent (do nothing) 131 } 132 133 @Override 134 public void setHomeButtonEnabled(boolean enabled) { 135 // If the nav button on a Toolbar is present, it's enabled. No-op. 136 } 137 138 @Override 139 public void setElevation(float elevation) { 140 mDecorToolbar.getViewGroup().setElevation(elevation); 141 } 142 143 @Override 144 public float getElevation() { 145 return mDecorToolbar.getViewGroup().getElevation(); 146 } 147 148 @Override 149 public Context getThemedContext() { 150 return mDecorToolbar.getContext(); 151 } 152 153 @Override 154 public boolean isTitleTruncated() { 155 return super.isTitleTruncated(); 156 } 157 158 @Override 159 public void setHomeAsUpIndicator(Drawable indicator) { 160 mDecorToolbar.setNavigationIcon(indicator); 161 } 162 163 @Override 164 public void setHomeAsUpIndicator(int resId) { 165 mDecorToolbar.setNavigationIcon(resId); 166 } 167 168 @Override 169 public void setHomeActionContentDescription(CharSequence description) { 170 mDecorToolbar.setNavigationContentDescription(description); 171 } 172 173 @Override 174 public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) { 175 // Do nothing 176 } 177 178 @Override 179 public void setHomeActionContentDescription(int resId) { 180 mDecorToolbar.setNavigationContentDescription(resId); 181 } 182 183 @Override 184 public void setShowHideAnimationEnabled(boolean enabled) { 185 // This space for rent; no-op. 186 } 187 188 @Override 189 public void onConfigurationChanged(Configuration config) { 190 super.onConfigurationChanged(config); 191 } 192 193 @Override 194 public ActionMode startActionMode(ActionMode.Callback callback) { 195 return null; 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 mDecorToolbar.getViewGroup().postOnAnimation(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 if (!mMenuCallbackSet) { 442 mDecorToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(), new MenuBuilderCallback()); 443 mMenuCallbackSet = true; 444 } 445 final Menu menu = mDecorToolbar.getMenu(); 446 final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null; 447 if (mb != null) { 448 mb.stopDispatchingItemsChanged(); 449 } 450 try { 451 menu.clear(); 452 if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) || 453 !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) { 454 menu.clear(); 455 } 456 } finally { 457 if (mb != null) { 458 mb.startDispatchingItemsChanged(); 459 } 460 } 461 } 462 463 @Override 464 public boolean onMenuKeyEvent(KeyEvent event) { 465 if (event.getAction() == KeyEvent.ACTION_UP) { 466 openOptionsMenu(); 467 } 468 return true; 469 } 470 471 @Override 472 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 473 Menu menu = mDecorToolbar.getMenu(); 474 if (menu != null) { 475 final KeyCharacterMap kmap = KeyCharacterMap.load( 476 event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD); 477 menu.setQwertyMode(kmap.getKeyboardType() != KeyCharacterMap.NUMERIC); 478 menu.performShortcut(keyCode, event, 0); 479 } 480 // This action bar always returns true for handling keyboard shortcuts. 481 // This will block the window from preparing a temporary panel to handle 482 // keyboard shortcuts. 483 return true; 484 } 485 486 @Override 487 public void onDestroy() { 488 // Remove any invalidation callbacks 489 mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator); 490 } 491 492 public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) { 493 mMenuVisibilityListeners.add(listener); 494 } 495 496 public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) { 497 mMenuVisibilityListeners.remove(listener); 498 } 499 500 public void dispatchMenuVisibilityChanged(boolean isVisible) { 501 if (isVisible == mLastMenuVisibility) { 502 return; 503 } 504 mLastMenuVisibility = isVisible; 505 506 final int count = mMenuVisibilityListeners.size(); 507 for (int i = 0; i < count; i++) { 508 mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible); 509 } 510 } 511 512 /** @hide */ 513 @Override 514 public boolean requestFocus() { 515 return requestFocus(mDecorToolbar.getViewGroup()); 516 } 517 518 private class ToolbarCallbackWrapper extends WindowCallbackWrapper { 519 public ToolbarCallbackWrapper(Window.Callback wrapped) { 520 super(wrapped); 521 } 522 523 @Override 524 public boolean onPreparePanel(int featureId, View view, Menu menu) { 525 final boolean result = super.onPreparePanel(featureId, view, menu); 526 if (result && !mToolbarMenuPrepared) { 527 mDecorToolbar.setMenuPrepared(); 528 mToolbarMenuPrepared = true; 529 } 530 return result; 531 } 532 } 533 534 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { 535 private boolean mClosingActionMenu; 536 537 @Override 538 public boolean onOpenSubMenu(MenuBuilder subMenu) { 539 if (mWindowCallback != null) { 540 mWindowCallback.onMenuOpened(Window.FEATURE_ACTION_BAR, subMenu); 541 return true; 542 } 543 return false; 544 } 545 546 @Override 547 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 548 if (mClosingActionMenu) { 549 return; 550 } 551 552 mClosingActionMenu = true; 553 mDecorToolbar.dismissPopupMenus(); 554 if (mWindowCallback != null) { 555 mWindowCallback.onPanelClosed(Window.FEATURE_ACTION_BAR, menu); 556 } 557 mClosingActionMenu = false; 558 } 559 } 560 561 private final class MenuBuilderCallback implements MenuBuilder.Callback { 562 563 @Override 564 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 565 return false; 566 } 567 568 @Override 569 public void onMenuModeChange(MenuBuilder menu) { 570 if (mWindowCallback != null) { 571 if (mDecorToolbar.isOverflowMenuShowing()) { 572 mWindowCallback.onPanelClosed(Window.FEATURE_ACTION_BAR, menu); 573 } else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, 574 null, menu)) { 575 mWindowCallback.onMenuOpened(Window.FEATURE_ACTION_BAR, menu); 576 } 577 } 578 } 579 } 580 } 581