ActionBarActivityDelegateBase.java revision dea1122145ed85590d298a3a9d88ba9f9e2f5f46
1/* 2 * Copyright (C) 2013 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.app; 18 19import android.content.Context; 20import android.content.res.Configuration; 21import android.content.res.Resources; 22import android.content.res.TypedArray; 23import android.os.Bundle; 24import android.support.v4.app.NavUtils; 25import android.support.v4.view.ViewCompat; 26import android.support.v4.view.ViewConfigurationCompat; 27import android.support.v4.view.WindowCompat; 28import android.support.v7.appcompat.R; 29import android.support.v7.internal.app.ToolbarActionBar; 30import android.support.v7.internal.app.WindowDecorActionBar; 31import android.support.v7.internal.view.StandaloneActionMode; 32import android.support.v7.internal.view.menu.ListMenuPresenter; 33import android.support.v7.internal.view.menu.MenuBuilder; 34import android.support.v7.internal.view.menu.MenuPresenter; 35import android.support.v7.internal.view.menu.MenuView; 36import android.support.v7.internal.widget.ActionBarContextView; 37import android.support.v7.internal.widget.DecorContentParent; 38import android.support.v7.internal.widget.ProgressBarCompat; 39import android.support.v7.view.ActionMode; 40import android.support.v7.widget.Toolbar; 41import android.util.DisplayMetrics; 42import android.util.TypedValue; 43import android.view.ContextThemeWrapper; 44import android.view.Gravity; 45import android.view.KeyEvent; 46import android.view.LayoutInflater; 47import android.view.Menu; 48import android.view.MenuItem; 49import android.view.View; 50import android.view.ViewConfiguration; 51import android.view.ViewGroup; 52import android.view.ViewStub; 53import android.view.Window; 54import android.view.accessibility.AccessibilityEvent; 55import android.widget.FrameLayout; 56import android.widget.PopupWindow; 57 58class ActionBarActivityDelegateBase extends ActionBarActivityDelegate 59 implements MenuBuilder.Callback { 60 private static final String TAG = "ActionBarActivityDelegateBase"; 61 62 private DecorContentParent mDecorContentParent; 63 private ActionMenuPresenterCallback mActionMenuPresenterCallback; 64 private PanelMenuPresenterCallback mPanelMenuPresenterCallback; 65 66 private ListMenuPresenter mListMenuPresenter; 67 private MenuBuilder mMenu; 68 69 ActionMode mActionMode; 70 ActionBarContextView mActionModeView; 71 PopupWindow mActionModePopup; 72 Runnable mShowActionModePopup; 73 74 // true if we have installed a window sub-decor layout. 75 private boolean mSubDecorInstalled; 76 private ViewGroup mWindowDecor; 77 78 private CharSequence mTitleToSet; 79 80 // Used to keep track of Progress Bar Window features 81 private boolean mFeatureProgress, mFeatureIndeterminateProgress; 82 83 // Used for emulating PanelFeatureState 84 private boolean mClosingActionMenu; 85 private boolean mPanelIsPrepared; 86 private boolean mPanelRefreshMenuContent; 87 private Bundle mPanelFrozenActionViewState; 88 89 private boolean mInvalidatePanelMenuPosted; 90 private final Runnable mInvalidatePanelMenuRunnable = new Runnable() { 91 @Override 92 public void run() { 93 supportInvalidateOptionsMenu(); 94 } 95 }; 96 97 private boolean mEnableDefaultActionBarUp; 98 99 ActionBarActivityDelegateBase(ActionBarActivity activity) { 100 super(activity); 101 } 102 103 @Override 104 void onCreate(Bundle savedInstanceState) { 105 super.onCreate(savedInstanceState); 106 107 mWindowDecor = (ViewGroup) mActivity.getWindow().getDecorView(); 108 109 if (NavUtils.getParentActivityName(mActivity) != null) { 110 ActionBar ab = getSupportActionBar(); 111 if (ab == null) { 112 mEnableDefaultActionBarUp = true; 113 } else { 114 ab.setDefaultDisplayHomeAsUpEnabled(true); 115 } 116 } 117 } 118 119 @Override 120 public ActionBar createSupportActionBar() { 121 ensureSubDecor(); 122 ActionBar ab = new WindowDecorActionBar(mActivity, mOverlayActionBar); 123 ab.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); 124 return ab; 125 } 126 127 @Override 128 void setSupportActionBar(Toolbar toolbar) { 129 if (getSupportActionBar() instanceof WindowDecorActionBar) { 130 throw new IllegalStateException("This Activity already has an action bar supplied " + 131 "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " + 132 "windowActionBar to false in your theme to use a Toolbar instead."); 133 } 134 // Need to make sure we give the action bar the default window callback. Otherwise multiple 135 // setSupportActionBar() calls lead to memory leaks 136 ToolbarActionBar tbab = new ToolbarActionBar(toolbar, mActivity.getTitle(), 137 mDefaultWindowCallback); 138 setSupportActionBar(tbab); 139 setWindowCallback(tbab.getWrappedWindowCallback()); 140 tbab.invalidateOptionsMenu(); 141 } 142 143 @Override 144 public void onConfigurationChanged(Configuration newConfig) { 145 // If this is called before sub-decor is installed, ActionBar will not 146 // be properly initialized. 147 if (mHasActionBar && mSubDecorInstalled) { 148 // Note: The action bar will need to access 149 // view changes from superclass. 150 ActionBar ab = getSupportActionBar(); 151 if (ab != null) { 152 ab.onConfigurationChanged(newConfig); 153 } 154 } 155 } 156 157 @Override 158 public void onStop() { 159 ActionBar ab = getSupportActionBar(); 160 if (ab != null) { 161 ab.setShowHideAnimationEnabled(false); 162 } 163 } 164 165 @Override 166 public void onPostResume() { 167 ActionBar ab = getSupportActionBar(); 168 if (ab != null) { 169 ab.setShowHideAnimationEnabled(true); 170 } 171 } 172 173 @Override 174 public void setContentView(View v) { 175 ensureSubDecor(); 176 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 177 contentParent.removeAllViews(); 178 contentParent.addView(v); 179 mActivity.onSupportContentChanged(); 180 } 181 182 @Override 183 public void setContentView(int resId) { 184 ensureSubDecor(); 185 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 186 contentParent.removeAllViews(); 187 mActivity.getLayoutInflater().inflate(resId, contentParent); 188 mActivity.onSupportContentChanged(); 189 } 190 191 @Override 192 public void setContentView(View v, ViewGroup.LayoutParams lp) { 193 ensureSubDecor(); 194 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 195 contentParent.removeAllViews(); 196 contentParent.addView(v, lp); 197 mActivity.onSupportContentChanged(); 198 } 199 200 @Override 201 public void addContentView(View v, ViewGroup.LayoutParams lp) { 202 ensureSubDecor(); 203 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 204 contentParent.addView(v, lp); 205 mActivity.onSupportContentChanged(); 206 } 207 208 @Override 209 public void onContentChanged() { 210 // Ignore all calls to this method as we call onSupportContentChanged manually above 211 } 212 213 final void ensureSubDecor() { 214 if (!mSubDecorInstalled) { 215 if (mHasActionBar) { 216 /** 217 * This needs some explanation. As we can not use the android:theme attribute 218 * pre-L, we emulate it by manually creating a LayoutInflater using a 219 * ContextThemeWrapper pointing to actionBarTheme. 220 */ 221 TypedValue outValue = new TypedValue(); 222 mActivity.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true); 223 224 Context themedContext; 225 if (outValue.resourceId != 0) { 226 themedContext = new ContextThemeWrapper(mActivity, outValue.resourceId); 227 } else { 228 themedContext = mActivity; 229 } 230 231 // Now inflate the view using the themed context and set it as the content view 232 View decor = LayoutInflater.from(themedContext) 233 .inflate(R.layout.abc_screen_toolbar, null); 234 mActivity.superSetContentView(decor); 235 236 mDecorContentParent = (DecorContentParent) mActivity 237 .findViewById(R.id.decor_content_parent); 238 mDecorContentParent.setWindowCallback(getWindowCallback()); 239 240 /** 241 * Propagate features to DecorContentParent 242 */ 243 if (mOverlayActionBar) { 244 mDecorContentParent.initFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY); 245 } 246 if (mFeatureProgress) { 247 mDecorContentParent.initFeature(Window.FEATURE_PROGRESS); 248 } 249 if (mFeatureIndeterminateProgress) { 250 mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 251 } 252 } else if (mOverlayActionMode) { 253 mActivity.superSetContentView(R.layout.abc_screen_simple_overlay_action_mode); 254 } else { 255 mActivity.superSetContentView(R.layout.abc_screen_simple); 256 } 257 258 // Change our content FrameLayout to use the android.R.id.content id. 259 // Useful for fragments. 260 final View decorContent = mActivity.findViewById(android.R.id.content); 261 decorContent.setId(View.NO_ID); 262 View abcContent = mActivity.findViewById(R.id.action_bar_activity_content); 263 abcContent.setId(android.R.id.content); 264 265 // The decorContent may have a foreground drawable set (windowContentOverlay). 266 // Remove this as we handle it ourselves 267 if (decorContent instanceof FrameLayout) { 268 ((FrameLayout) decorContent).setForeground(null); 269 } 270 271 // A title was set before we've install the decor so set it now. 272 if (mTitleToSet != null && mDecorContentParent != null) { 273 mDecorContentParent.setWindowTitle(mTitleToSet); 274 mTitleToSet = null; 275 } 276 277 applyFixedSizeWindow(); 278 279 onSubDecorInstalled(); 280 281 mSubDecorInstalled = true; 282 283 invalidatePanelMenu(); 284 } 285 } 286 287 void onSubDecorInstalled() {} 288 289 private void applyFixedSizeWindow() { 290 TypedArray a = mActivity.obtainStyledAttributes(R.styleable.Theme); 291 292 TypedValue mFixedWidthMajor = null; 293 TypedValue mFixedWidthMinor = null; 294 TypedValue mFixedHeightMajor = null; 295 TypedValue mFixedHeightMinor = null; 296 297 if (a.hasValue(R.styleable.Theme_windowFixedWidthMajor)) { 298 if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue(); 299 a.getValue(R.styleable.Theme_windowFixedWidthMajor, mFixedWidthMajor); 300 } 301 if (a.hasValue(R.styleable.Theme_windowFixedWidthMinor)) { 302 if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue(); 303 a.getValue(R.styleable.Theme_windowFixedWidthMinor, mFixedWidthMinor); 304 } 305 if (a.hasValue(R.styleable.Theme_windowFixedHeightMajor)) { 306 if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue(); 307 a.getValue(R.styleable.Theme_windowFixedHeightMajor, mFixedHeightMajor); 308 } 309 if (a.hasValue(R.styleable.Theme_windowFixedHeightMinor)) { 310 if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue(); 311 a.getValue(R.styleable.Theme_windowFixedHeightMinor, mFixedHeightMinor); 312 } 313 314 final DisplayMetrics metrics = mActivity.getResources().getDisplayMetrics(); 315 final boolean isPortrait = metrics.widthPixels < metrics.heightPixels; 316 int w = ViewGroup.LayoutParams.MATCH_PARENT; 317 int h = ViewGroup.LayoutParams.MATCH_PARENT; 318 319 final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor; 320 if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { 321 if (tvw.type == TypedValue.TYPE_DIMENSION) { 322 w = (int) tvw.getDimension(metrics); 323 } else if (tvw.type == TypedValue.TYPE_FRACTION) { 324 w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels); 325 } 326 } 327 328 final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor; 329 if (tvh != null && tvh.type != TypedValue.TYPE_NULL) { 330 if (tvh.type == TypedValue.TYPE_DIMENSION) { 331 h = (int) tvh.getDimension(metrics); 332 } else if (tvh.type == TypedValue.TYPE_FRACTION) { 333 h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels); 334 } 335 } 336 337 if (w != ViewGroup.LayoutParams.MATCH_PARENT || h != ViewGroup.LayoutParams.MATCH_PARENT) { 338 mActivity.getWindow().setLayout(w, h); 339 } 340 341 a.recycle(); 342 } 343 344 @Override 345 public boolean supportRequestWindowFeature(int featureId) { 346 switch (featureId) { 347 case WindowCompat.FEATURE_ACTION_BAR: 348 mHasActionBar = true; 349 return true; 350 case WindowCompat.FEATURE_ACTION_BAR_OVERLAY: 351 mOverlayActionBar = true; 352 return true; 353 case WindowCompat.FEATURE_ACTION_MODE_OVERLAY: 354 mOverlayActionMode = true; 355 return true; 356 case Window.FEATURE_PROGRESS: 357 mFeatureProgress = true; 358 return true; 359 case Window.FEATURE_INDETERMINATE_PROGRESS: 360 mFeatureIndeterminateProgress = true; 361 return true; 362 default: 363 return mActivity.requestWindowFeature(featureId); 364 } 365 } 366 367 @Override 368 public void onTitleChanged(CharSequence title) { 369 if (mDecorContentParent != null) { 370 mDecorContentParent.setWindowTitle(title); 371 } else if (getSupportActionBar() != null) { 372 getSupportActionBar().setWindowTitle(title); 373 } else { 374 mTitleToSet = title; 375 } 376 } 377 378 @Override 379 public View onCreatePanelView(int featureId) { 380 View createdPanelView = null; 381 382 if (featureId == Window.FEATURE_OPTIONS_PANEL && preparePanel()) { 383 createdPanelView = (View) getListMenuView(mActivity); 384 } 385 386 return createdPanelView; 387 } 388 389 @Override 390 public boolean onCreatePanelMenu(int featureId, Menu menu) { 391 if (featureId != Window.FEATURE_OPTIONS_PANEL) { 392 return getWindowCallback().onCreatePanelMenu(featureId, menu); 393 } 394 return false; 395 } 396 397 @Override 398 public boolean onPreparePanel(int featureId, View view, Menu menu) { 399 if (featureId != Window.FEATURE_OPTIONS_PANEL) { 400 return getWindowCallback().onPreparePanel(featureId, view, menu); 401 } 402 return false; 403 } 404 405 @Override 406 public void onPanelClosed(int featureId, Menu menu) { 407 if (featureId == Window.FEATURE_OPTIONS_PANEL) { 408 mPanelIsPrepared = false; 409 } 410 } 411 412 @Override 413 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 414 return mActivity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); 415 } 416 417 @Override 418 public void onMenuModeChange(MenuBuilder menu) { 419 reopenMenu(menu, true); 420 } 421 422 @Override 423 public ActionMode startSupportActionMode(ActionMode.Callback callback) { 424 if (callback == null) { 425 throw new IllegalArgumentException("ActionMode callback can not be null."); 426 } 427 428 if (mActionMode != null) { 429 mActionMode.finish(); 430 } 431 432 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); 433 434 ActionBar ab = getSupportActionBar(); 435 if (ab != null) { 436 mActionMode = ab.startActionMode(wrappedCallback); 437 if (mActionMode != null) { 438 mActivity.onSupportActionModeStarted(mActionMode); 439 } 440 } 441 442 if (mActionMode == null) { 443 // If the action bar didn't provide an action mode, start the emulated window one 444 mActionMode = startSupportActionModeFromWindow(wrappedCallback); 445 } 446 447 return mActionMode; 448 } 449 450 @Override 451 public void supportInvalidateOptionsMenu() { 452 final ActionBar ab = getSupportActionBar(); 453 if (ab != null && ab.invalidateOptionsMenu()) return; 454 455 if (mMenu != null) { 456 Bundle savedActionViewStates = new Bundle(); 457 mMenu.saveActionViewStates(savedActionViewStates); 458 if (savedActionViewStates.size() > 0) { 459 mPanelFrozenActionViewState = savedActionViewStates; 460 } 461 // This will be started again when the panel is prepared. 462 mMenu.stopDispatchingItemsChanged(); 463 mMenu.clear(); 464 } 465 mPanelRefreshMenuContent = true; 466 467 // Prepare the options panel if we have an action bar 468 if (mDecorContentParent != null) { 469 mPanelIsPrepared = false; 470 preparePanel(); 471 } 472 } 473 474 @Override 475 ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback) { 476 if (mActionMode != null) { 477 mActionMode.finish(); 478 } 479 480 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); 481 ActionMode mode = null; 482 483 if (mActionModeView == null) { 484 if (mIsFloating) { 485 mActionModeView = new ActionBarContextView(mActivity); 486 mActionModePopup = new PopupWindow(mActivity, null, 487 R.attr.actionModePopupWindowStyle); 488 mActionModePopup.setContentView(mActionModeView); 489 mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); 490 491 TypedValue heightValue = new TypedValue(); 492 mActivity.getTheme().resolveAttribute(R.attr.actionBarSize, heightValue, true); 493 final int height = TypedValue.complexToDimensionPixelSize(heightValue.data, 494 mActivity.getResources().getDisplayMetrics()); 495 mActionModeView.setContentHeight(height); 496 mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); 497 mShowActionModePopup = new Runnable() { 498 public void run() { 499 mActionModePopup.showAtLocation( 500 mActionModeView, 501 Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); 502 } 503 }; 504 } else { 505 ViewStub stub = (ViewStub) mActivity.findViewById(R.id.action_mode_bar_stub); 506 if (stub != null) { 507 mActionModeView = (ActionBarContextView) stub.inflate(); 508 } 509 } 510 } 511 512 if (mActionModeView != null) { 513 mActionModeView.killMode(); 514 mode = new StandaloneActionMode(mActivity, mActionModeView, wrappedCallback, 515 mActionModePopup == null); 516 if (callback.onCreateActionMode(mode, mode.getMenu())) { 517 mode.invalidate(); 518 mActionModeView.initForMode(mode); 519 mActionModeView.setVisibility(View.VISIBLE); 520 mActionMode = mode; 521 if (mActionModePopup != null) { 522 mActivity.getWindow().getDecorView().post(mShowActionModePopup); 523 } 524 mActionModeView.sendAccessibilityEvent( 525 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 526 } else { 527 mActionMode = null; 528 } 529 } 530 if (mActionMode != null && mActivity != null) { 531 mActivity.onSupportActionModeStarted(mActionMode); 532 } 533 return mActionMode; 534 } 535 536 private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) { 537 if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() && 538 (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity)) || 539 mDecorContentParent.isOverflowMenuShowPending())) { 540 if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) { 541 542 // If we have a menu invalidation pending, do it now. 543 if (mInvalidatePanelMenuPosted) { 544 mWindowDecor.removeCallbacks(mInvalidatePanelMenuRunnable); 545 mInvalidatePanelMenuRunnable.run(); 546 } 547 548 // If we don't have a menu or we're waiting for a full content refresh, 549 // forget it. This is a lingering event that no longer matters. 550 if (mMenu != null && !mPanelRefreshMenuContent && preparePanel()) { 551 mDecorContentParent.showOverflowMenu(); 552 } 553 } else { 554 mDecorContentParent.hideOverflowMenu(); 555 } 556 return; 557 } 558 } 559 560 private MenuView getListMenuView(Context context) { 561 if (mMenu == null) { 562 return null; 563 } 564 565 if (mPanelMenuPresenterCallback == null) { 566 mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); 567 } 568 569 if (mListMenuPresenter == null) { 570 TypedArray a = context.obtainStyledAttributes(R.styleable.Theme); 571 final int listPresenterTheme = a.getResourceId( 572 R.styleable.Theme_panelMenuListTheme, 573 R.style.Theme_AppCompat_CompactMenu); 574 a.recycle(); 575 576 mListMenuPresenter = new ListMenuPresenter( 577 R.layout.abc_list_menu_item_layout, listPresenterTheme); 578 mListMenuPresenter.setCallback(mPanelMenuPresenterCallback); 579 mMenu.addMenuPresenter(mListMenuPresenter, mActivity); 580 } else { 581 // Make sure we update the ListView 582 mListMenuPresenter.updateMenuView(false); 583 } 584 585 if (mListMenuPresenter.getAdapter().isEmpty()) { 586 return null; 587 } 588 589 return mListMenuPresenter.getMenuView(mWindowDecor); 590 } 591 592 @Override 593 public boolean onBackPressed() { 594 // Back cancels action modes first. 595 if (mActionMode != null) { 596 mActionMode.finish(); 597 return true; 598 } 599 600 // Next collapse any expanded action views. 601 ActionBar ab = getSupportActionBar(); 602 if (ab != null && ab.collapseActionView()) { 603 return true; 604 } 605 606 return false; 607 } 608 609 @Override 610 void setSupportProgressBarVisibility(boolean visible) { 611 updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON : 612 Window.PROGRESS_VISIBILITY_OFF); 613 } 614 615 @Override 616 void setSupportProgressBarIndeterminateVisibility(boolean visible) { 617 updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON : 618 Window.PROGRESS_VISIBILITY_OFF); 619 } 620 621 @Override 622 void setSupportProgressBarIndeterminate(boolean indeterminate) { 623 updateProgressBars(indeterminate ? Window.PROGRESS_INDETERMINATE_ON 624 : Window.PROGRESS_INDETERMINATE_OFF); 625 } 626 627 @Override 628 void setSupportProgress(int progress) { 629 updateProgressBars(Window.PROGRESS_START + progress); 630 } 631 632 @Override 633 int getHomeAsUpIndicatorAttrId() { 634 return R.attr.homeAsUpIndicator; 635 } 636 637 @Override 638 boolean onKeyShortcut(int keyCode, KeyEvent event) { 639 // If the panel is already prepared, then perform the shortcut using it. 640 return performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, event.getKeyCode(), event, 641 Menu.FLAG_PERFORM_NO_CLOSE); 642 } 643 644 @Override 645 boolean onKeyDown(int keyCode, KeyEvent event) { 646 // On API v7-10 we need to manually call onKeyShortcut() as this is not called 647 // from the Activity 648 return onKeyShortcut(keyCode, event); 649 } 650 651 /** 652 * Progress Bar function. Mostly extracted from PhoneWindow.java 653 */ 654 private void updateProgressBars(int value) { 655 ProgressBarCompat circularProgressBar = getCircularProgressBar(); 656 ProgressBarCompat horizontalProgressBar = getHorizontalProgressBar(); 657 658 if (value == Window.PROGRESS_VISIBILITY_ON) { 659 if (mFeatureProgress) { 660 int level = horizontalProgressBar.getProgress(); 661 int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? 662 View.VISIBLE : View.INVISIBLE; 663 horizontalProgressBar.setVisibility(visibility); 664 } 665 if (mFeatureIndeterminateProgress) { 666 circularProgressBar.setVisibility(View.VISIBLE); 667 } 668 } else if (value == Window.PROGRESS_VISIBILITY_OFF) { 669 if (mFeatureProgress) { 670 horizontalProgressBar.setVisibility(View.GONE); 671 } 672 if (mFeatureIndeterminateProgress) { 673 circularProgressBar.setVisibility(View.GONE); 674 } 675 } else if (value == Window.PROGRESS_INDETERMINATE_ON) { 676 horizontalProgressBar.setIndeterminate(true); 677 } else if (value == Window.PROGRESS_INDETERMINATE_OFF) { 678 horizontalProgressBar.setIndeterminate(false); 679 } else if (Window.PROGRESS_START <= value && value <= Window.PROGRESS_END) { 680 // We want to set the progress value before testing for visibility 681 // so that when the progress bar becomes visible again, it has the 682 // correct level. 683 horizontalProgressBar.setProgress(value - Window.PROGRESS_START); 684 685 if (value < Window.PROGRESS_END) { 686 showProgressBars(horizontalProgressBar, circularProgressBar); 687 } else { 688 hideProgressBars(horizontalProgressBar, circularProgressBar); 689 } 690 } 691 } 692 693 private void showProgressBars(ProgressBarCompat horizontalProgressBar, 694 ProgressBarCompat spinnyProgressBar) { 695 if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.INVISIBLE) { 696 spinnyProgressBar.setVisibility(View.VISIBLE); 697 } 698 // Only show the progress bars if the primary progress is not complete 699 if (mFeatureProgress && horizontalProgressBar.getProgress() < 10000) { 700 horizontalProgressBar.setVisibility(View.VISIBLE); 701 } 702 } 703 704 private void hideProgressBars(ProgressBarCompat horizontalProgressBar, 705 ProgressBarCompat spinnyProgressBar) { 706 if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.VISIBLE) { 707 spinnyProgressBar.setVisibility(View.INVISIBLE); 708 } 709 if (mFeatureProgress && horizontalProgressBar.getVisibility() == View.VISIBLE) { 710 horizontalProgressBar.setVisibility(View.INVISIBLE); 711 } 712 } 713 714 private ProgressBarCompat getCircularProgressBar() { 715 ProgressBarCompat pb = (ProgressBarCompat) mActivity.findViewById(R.id.progress_circular); 716 if (pb != null) { 717 pb.setVisibility(View.INVISIBLE); 718 } 719 return pb; 720 } 721 722 private ProgressBarCompat getHorizontalProgressBar() { 723 ProgressBarCompat pb = (ProgressBarCompat) mActivity.findViewById(R.id.progress_horizontal); 724 if (pb != null) { 725 pb.setVisibility(View.INVISIBLE); 726 } 727 return pb; 728 } 729 730 private boolean initializePanelMenu() { 731 Context context = mActivity; 732 733 if (mDecorContentParent != null) { 734 final TypedValue outValue = new TypedValue(); 735 final Resources.Theme baseTheme = context.getTheme(); 736 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); 737 738 Resources.Theme widgetTheme = null; 739 if (outValue.resourceId != 0) { 740 widgetTheme = context.getResources().newTheme(); 741 widgetTheme.setTo(baseTheme); 742 widgetTheme.applyStyle(outValue.resourceId, true); 743 widgetTheme.resolveAttribute( 744 R.attr.actionBarWidgetTheme, outValue, true); 745 } else { 746 baseTheme.resolveAttribute( 747 R.attr.actionBarWidgetTheme, outValue, true); 748 } 749 750 if (outValue.resourceId != 0) { 751 if (widgetTheme == null) { 752 widgetTheme = context.getResources().newTheme(); 753 widgetTheme.setTo(baseTheme); 754 } 755 widgetTheme.applyStyle(outValue.resourceId, true); 756 } 757 758 if (widgetTheme != null) { 759 context = new ContextThemeWrapper(context, 0); 760 context.getTheme().setTo(widgetTheme); 761 } 762 } 763 764 mMenu = new MenuBuilder(context); 765 mMenu.setCallback(this); 766 767 return true; 768 } 769 770 private boolean preparePanel() { 771 // Already prepared (isPrepared will be reset to false later) 772 if (mPanelIsPrepared) { 773 return true; 774 } 775 776 if (mDecorContentParent != null) { 777 // Enforce ordering guarantees around events so that the action bar never 778 // dispatches menu-related events before the panel is prepared. 779 mDecorContentParent.setMenuPrepared(); 780 } 781 782 // Init the panel state's menu--return false if init failed 783 if (mMenu == null || mPanelRefreshMenuContent) { 784 if (mMenu == null) { 785 if (!initializePanelMenu() || (mMenu == null)) { 786 return false; 787 } 788 } 789 790 if (mDecorContentParent != null) { 791 if (mActionMenuPresenterCallback == null) { 792 mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); 793 } 794 mDecorContentParent.setMenu(mMenu, mActionMenuPresenterCallback); 795 } 796 797 // Creating the panel menu will involve a lot of manipulation; 798 // don't dispatch change events to presenters until we're done. 799 mMenu.stopDispatchingItemsChanged(); 800 801 // Call callback, and return if it doesn't want to display menu. 802 if (!getWindowCallback().onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, mMenu)) { 803 // Ditch the menu created above 804 mMenu = null; 805 806 if (mDecorContentParent != null) { 807 // Don't show it in the action bar either 808 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 809 } 810 811 return false; 812 } 813 814 mPanelRefreshMenuContent = false; 815 } 816 817 // Preparing the panel menu can involve a lot of manipulation; 818 // don't dispatch change events to presenters until we're done. 819 mMenu.stopDispatchingItemsChanged(); 820 821 // Restore action view state before we prepare. This gives apps 822 // an opportunity to override frozen/restored state in onPrepare. 823 if (mPanelFrozenActionViewState != null) { 824 mMenu.restoreActionViewStates(mPanelFrozenActionViewState); 825 mPanelFrozenActionViewState = null; 826 } 827 828 // Callback and return if the callback does not want to show the menu 829 if (!getWindowCallback().onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, mMenu)) { 830 if (mDecorContentParent != null) { 831 // The app didn't want to show the menu for now but it still exists. 832 // Clear it out of the action bar. 833 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 834 } 835 mMenu.startDispatchingItemsChanged(); 836 return false; 837 } 838 839 mMenu.startDispatchingItemsChanged(); 840 841 // Set other state 842 mPanelIsPrepared = true; 843 844 return true; 845 } 846 847 private void checkCloseActionMenu() { 848 if (mClosingActionMenu) { 849 return; 850 } 851 852 mClosingActionMenu = true; 853 mDecorContentParent.dismissPopups(); 854 mClosingActionMenu = false; 855 } 856 857 private void closePanel(int featureId) { 858 if (featureId == Window.FEATURE_OPTIONS_PANEL && mDecorContentParent != null && 859 mDecorContentParent.canShowOverflowMenu() && 860 !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity))) { 861 mDecorContentParent.hideOverflowMenu(); 862 } else { 863 mActivity.closeOptionsMenu(); 864 mPanelIsPrepared = false; 865 } 866 } 867 868 private void invalidatePanelMenu() { 869 if (!mInvalidatePanelMenuPosted && mWindowDecor != null) { 870 ViewCompat.postOnAnimation(mWindowDecor, mInvalidatePanelMenuRunnable); 871 mInvalidatePanelMenuPosted = true; 872 } 873 } 874 875 final boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 876 if (event.isSystem()) { 877 return false; 878 } 879 880 boolean handled = false; 881 882 // Only try to perform menu shortcuts if preparePanel returned true (possible false 883 // return value from application not wanting to show the menu). 884 if ((mPanelIsPrepared || preparePanel()) && mMenu != null) { 885 // The menu is prepared now, perform the shortcut on it 886 handled = mMenu.performShortcut(keyCode, event, flags); 887 } 888 889 if (handled) { 890 // Only close down the menu if we don't have an action bar keeping it open. 891 if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) { 892 closePanel(featureId); 893 } 894 } 895 896 return handled; 897 } 898 899 /** 900 * Clears out internal reference when the action mode is destroyed. 901 */ 902 private class ActionModeCallbackWrapper implements ActionMode.Callback { 903 private ActionMode.Callback mWrapped; 904 905 public ActionModeCallbackWrapper(ActionMode.Callback wrapped) { 906 mWrapped = wrapped; 907 } 908 909 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 910 return mWrapped.onCreateActionMode(mode, menu); 911 } 912 913 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 914 return mWrapped.onPrepareActionMode(mode, menu); 915 } 916 917 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 918 return mWrapped.onActionItemClicked(mode, item); 919 } 920 921 public void onDestroyActionMode(ActionMode mode) { 922 mWrapped.onDestroyActionMode(mode); 923 if (mActionModePopup != null) { 924 mActivity.getWindow().getDecorView().removeCallbacks(mShowActionModePopup); 925 mActionModePopup.dismiss(); 926 } else if (mActionModeView != null) { 927 mActionModeView.setVisibility(View.GONE); 928 } 929 if (mActionModeView != null) { 930 mActionModeView.removeAllViews(); 931 } 932 if (mActivity != null) { 933 try { 934 mActivity.onSupportActionModeFinished(mActionMode); 935 } catch (AbstractMethodError ame) { 936 // Older apps might not implement this callback method. 937 } 938 } 939 mActionMode = null; 940 } 941 } 942 943 private final class PanelMenuPresenterCallback implements MenuPresenter.Callback { 944 @Override 945 public boolean onOpenSubMenu(MenuBuilder subMenu) { 946 return false; 947 } 948 949 @Override 950 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 951 closePanel(Window.FEATURE_OPTIONS_PANEL); 952 } 953 } 954 955 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { 956 @Override 957 public boolean onOpenSubMenu(MenuBuilder subMenu) { 958 return false; 959 } 960 961 @Override 962 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 963 checkCloseActionMenu(); 964 } 965 } 966 967} 968