ActionBarActivityDelegateBase.java revision c4b9e0cb716a4caff218b27d86f37ef8117d257b
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 // Only pass it through to the Activity's super impl if it's not ACTION_BAR. This is 411 // because ICS+ will try and create a framework action bar due to this call 412 if (featureId != WindowCompat.FEATURE_ACTION_BAR) { 413 mActivity.superOnPanelClosed(featureId, menu); 414 } 415 } 416 417 @Override 418 boolean onMenuOpened(int featureId, Menu menu) { 419 if (featureId != WindowCompat.FEATURE_ACTION_BAR) { 420 return mActivity.superOnMenuOpened(featureId, menu); 421 } 422 return true; 423 } 424 425 @Override 426 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 427 return mActivity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); 428 } 429 430 @Override 431 public void onMenuModeChange(MenuBuilder menu) { 432 reopenMenu(menu, true); 433 } 434 435 @Override 436 public ActionMode startSupportActionMode(ActionMode.Callback callback) { 437 if (callback == null) { 438 throw new IllegalArgumentException("ActionMode callback can not be null."); 439 } 440 441 if (mActionMode != null) { 442 mActionMode.finish(); 443 } 444 445 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); 446 447 ActionBar ab = getSupportActionBar(); 448 if (ab != null) { 449 mActionMode = ab.startActionMode(wrappedCallback); 450 if (mActionMode != null) { 451 mActivity.onSupportActionModeStarted(mActionMode); 452 } 453 } 454 455 if (mActionMode == null) { 456 // If the action bar didn't provide an action mode, start the emulated window one 457 mActionMode = startSupportActionModeFromWindow(wrappedCallback); 458 } 459 460 return mActionMode; 461 } 462 463 @Override 464 public void supportInvalidateOptionsMenu() { 465 final ActionBar ab = getSupportActionBar(); 466 if (ab != null && ab.invalidateOptionsMenu()) return; 467 468 if (mMenu != null) { 469 Bundle savedActionViewStates = new Bundle(); 470 mMenu.saveActionViewStates(savedActionViewStates); 471 if (savedActionViewStates.size() > 0) { 472 mPanelFrozenActionViewState = savedActionViewStates; 473 } 474 // This will be started again when the panel is prepared. 475 mMenu.stopDispatchingItemsChanged(); 476 mMenu.clear(); 477 } 478 mPanelRefreshMenuContent = true; 479 480 // Prepare the options panel if we have an action bar 481 if (mDecorContentParent != null) { 482 mPanelIsPrepared = false; 483 preparePanel(); 484 } 485 } 486 487 @Override 488 ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback) { 489 if (mActionMode != null) { 490 mActionMode.finish(); 491 } 492 493 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); 494 ActionMode mode = null; 495 496 if (mActionModeView == null) { 497 if (mIsFloating) { 498 mActionModeView = new ActionBarContextView(mActivity); 499 mActionModePopup = new PopupWindow(mActivity, null, 500 R.attr.actionModePopupWindowStyle); 501 mActionModePopup.setContentView(mActionModeView); 502 mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); 503 504 TypedValue heightValue = new TypedValue(); 505 mActivity.getTheme().resolveAttribute(R.attr.actionBarSize, heightValue, true); 506 final int height = TypedValue.complexToDimensionPixelSize(heightValue.data, 507 mActivity.getResources().getDisplayMetrics()); 508 mActionModeView.setContentHeight(height); 509 mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); 510 mShowActionModePopup = new Runnable() { 511 public void run() { 512 mActionModePopup.showAtLocation( 513 mActionModeView, 514 Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); 515 } 516 }; 517 } else { 518 ViewStub stub = (ViewStub) mActivity.findViewById(R.id.action_mode_bar_stub); 519 if (stub != null) { 520 mActionModeView = (ActionBarContextView) stub.inflate(); 521 } 522 } 523 } 524 525 if (mActionModeView != null) { 526 mActionModeView.killMode(); 527 mode = new StandaloneActionMode(mActivity, mActionModeView, wrappedCallback, 528 mActionModePopup == null); 529 if (callback.onCreateActionMode(mode, mode.getMenu())) { 530 mode.invalidate(); 531 mActionModeView.initForMode(mode); 532 mActionModeView.setVisibility(View.VISIBLE); 533 mActionMode = mode; 534 if (mActionModePopup != null) { 535 mActivity.getWindow().getDecorView().post(mShowActionModePopup); 536 } 537 mActionModeView.sendAccessibilityEvent( 538 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 539 } else { 540 mActionMode = null; 541 } 542 } 543 if (mActionMode != null && mActivity != null) { 544 mActivity.onSupportActionModeStarted(mActionMode); 545 } 546 return mActionMode; 547 } 548 549 private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) { 550 if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() && 551 (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity)) || 552 mDecorContentParent.isOverflowMenuShowPending())) { 553 if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) { 554 555 // If we have a menu invalidation pending, do it now. 556 if (mInvalidatePanelMenuPosted) { 557 mWindowDecor.removeCallbacks(mInvalidatePanelMenuRunnable); 558 mInvalidatePanelMenuRunnable.run(); 559 } 560 561 // If we don't have a menu or we're waiting for a full content refresh, 562 // forget it. This is a lingering event that no longer matters. 563 if (mMenu != null && !mPanelRefreshMenuContent && preparePanel()) { 564 mDecorContentParent.showOverflowMenu(); 565 } 566 } else { 567 mDecorContentParent.hideOverflowMenu(); 568 } 569 return; 570 } 571 } 572 573 private MenuView getListMenuView(Context context) { 574 if (mMenu == null) { 575 return null; 576 } 577 578 if (mPanelMenuPresenterCallback == null) { 579 mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); 580 } 581 582 if (mListMenuPresenter == null) { 583 TypedArray a = context.obtainStyledAttributes(R.styleable.Theme); 584 final int listPresenterTheme = a.getResourceId( 585 R.styleable.Theme_panelMenuListTheme, 586 R.style.Theme_AppCompat_CompactMenu); 587 a.recycle(); 588 589 mListMenuPresenter = new ListMenuPresenter( 590 R.layout.abc_list_menu_item_layout, listPresenterTheme); 591 mListMenuPresenter.setCallback(mPanelMenuPresenterCallback); 592 mMenu.addMenuPresenter(mListMenuPresenter, mActivity); 593 } else { 594 // Make sure we update the ListView 595 mListMenuPresenter.updateMenuView(false); 596 } 597 598 if (mListMenuPresenter.getAdapter().isEmpty()) { 599 return null; 600 } 601 602 return mListMenuPresenter.getMenuView(mWindowDecor); 603 } 604 605 @Override 606 public boolean onBackPressed() { 607 // Back cancels action modes first. 608 if (mActionMode != null) { 609 mActionMode.finish(); 610 return true; 611 } 612 613 // Next collapse any expanded action views. 614 ActionBar ab = getSupportActionBar(); 615 if (ab != null && ab.collapseActionView()) { 616 return true; 617 } 618 619 return false; 620 } 621 622 @Override 623 void setSupportProgressBarVisibility(boolean visible) { 624 updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON : 625 Window.PROGRESS_VISIBILITY_OFF); 626 } 627 628 @Override 629 void setSupportProgressBarIndeterminateVisibility(boolean visible) { 630 updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON : 631 Window.PROGRESS_VISIBILITY_OFF); 632 } 633 634 @Override 635 void setSupportProgressBarIndeterminate(boolean indeterminate) { 636 updateProgressBars(indeterminate ? Window.PROGRESS_INDETERMINATE_ON 637 : Window.PROGRESS_INDETERMINATE_OFF); 638 } 639 640 @Override 641 void setSupportProgress(int progress) { 642 updateProgressBars(Window.PROGRESS_START + progress); 643 } 644 645 @Override 646 int getHomeAsUpIndicatorAttrId() { 647 return R.attr.homeAsUpIndicator; 648 } 649 650 @Override 651 boolean onKeyShortcut(int keyCode, KeyEvent event) { 652 // If the panel is already prepared, then perform the shortcut using it. 653 return performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, event.getKeyCode(), event, 654 Menu.FLAG_PERFORM_NO_CLOSE); 655 } 656 657 @Override 658 boolean onKeyDown(int keyCode, KeyEvent event) { 659 // On API v7-10 we need to manually call onKeyShortcut() as this is not called 660 // from the Activity 661 return onKeyShortcut(keyCode, event); 662 } 663 664 /** 665 * Progress Bar function. Mostly extracted from PhoneWindow.java 666 */ 667 private void updateProgressBars(int value) { 668 ProgressBarCompat circularProgressBar = getCircularProgressBar(); 669 ProgressBarCompat horizontalProgressBar = getHorizontalProgressBar(); 670 671 if (value == Window.PROGRESS_VISIBILITY_ON) { 672 if (mFeatureProgress) { 673 int level = horizontalProgressBar.getProgress(); 674 int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? 675 View.VISIBLE : View.INVISIBLE; 676 horizontalProgressBar.setVisibility(visibility); 677 } 678 if (mFeatureIndeterminateProgress) { 679 circularProgressBar.setVisibility(View.VISIBLE); 680 } 681 } else if (value == Window.PROGRESS_VISIBILITY_OFF) { 682 if (mFeatureProgress) { 683 horizontalProgressBar.setVisibility(View.GONE); 684 } 685 if (mFeatureIndeterminateProgress) { 686 circularProgressBar.setVisibility(View.GONE); 687 } 688 } else if (value == Window.PROGRESS_INDETERMINATE_ON) { 689 horizontalProgressBar.setIndeterminate(true); 690 } else if (value == Window.PROGRESS_INDETERMINATE_OFF) { 691 horizontalProgressBar.setIndeterminate(false); 692 } else if (Window.PROGRESS_START <= value && value <= Window.PROGRESS_END) { 693 // We want to set the progress value before testing for visibility 694 // so that when the progress bar becomes visible again, it has the 695 // correct level. 696 horizontalProgressBar.setProgress(value - Window.PROGRESS_START); 697 698 if (value < Window.PROGRESS_END) { 699 showProgressBars(horizontalProgressBar, circularProgressBar); 700 } else { 701 hideProgressBars(horizontalProgressBar, circularProgressBar); 702 } 703 } 704 } 705 706 private void showProgressBars(ProgressBarCompat horizontalProgressBar, 707 ProgressBarCompat spinnyProgressBar) { 708 if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.INVISIBLE) { 709 spinnyProgressBar.setVisibility(View.VISIBLE); 710 } 711 // Only show the progress bars if the primary progress is not complete 712 if (mFeatureProgress && horizontalProgressBar.getProgress() < 10000) { 713 horizontalProgressBar.setVisibility(View.VISIBLE); 714 } 715 } 716 717 private void hideProgressBars(ProgressBarCompat horizontalProgressBar, 718 ProgressBarCompat spinnyProgressBar) { 719 if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.VISIBLE) { 720 spinnyProgressBar.setVisibility(View.INVISIBLE); 721 } 722 if (mFeatureProgress && horizontalProgressBar.getVisibility() == View.VISIBLE) { 723 horizontalProgressBar.setVisibility(View.INVISIBLE); 724 } 725 } 726 727 private ProgressBarCompat getCircularProgressBar() { 728 ProgressBarCompat pb = (ProgressBarCompat) mActivity.findViewById(R.id.progress_circular); 729 if (pb != null) { 730 pb.setVisibility(View.INVISIBLE); 731 } 732 return pb; 733 } 734 735 private ProgressBarCompat getHorizontalProgressBar() { 736 ProgressBarCompat pb = (ProgressBarCompat) mActivity.findViewById(R.id.progress_horizontal); 737 if (pb != null) { 738 pb.setVisibility(View.INVISIBLE); 739 } 740 return pb; 741 } 742 743 private boolean initializePanelMenu() { 744 Context context = mActivity; 745 746 if (mDecorContentParent != null) { 747 final TypedValue outValue = new TypedValue(); 748 final Resources.Theme baseTheme = context.getTheme(); 749 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); 750 751 Resources.Theme widgetTheme = null; 752 if (outValue.resourceId != 0) { 753 widgetTheme = context.getResources().newTheme(); 754 widgetTheme.setTo(baseTheme); 755 widgetTheme.applyStyle(outValue.resourceId, true); 756 widgetTheme.resolveAttribute( 757 R.attr.actionBarWidgetTheme, outValue, true); 758 } else { 759 baseTheme.resolveAttribute( 760 R.attr.actionBarWidgetTheme, outValue, true); 761 } 762 763 if (outValue.resourceId != 0) { 764 if (widgetTheme == null) { 765 widgetTheme = context.getResources().newTheme(); 766 widgetTheme.setTo(baseTheme); 767 } 768 widgetTheme.applyStyle(outValue.resourceId, true); 769 } 770 771 if (widgetTheme != null) { 772 context = new ContextThemeWrapper(context, 0); 773 context.getTheme().setTo(widgetTheme); 774 } 775 } 776 777 mMenu = new MenuBuilder(context); 778 mMenu.setCallback(this); 779 780 return true; 781 } 782 783 private boolean preparePanel() { 784 // Already prepared (isPrepared will be reset to false later) 785 if (mPanelIsPrepared) { 786 return true; 787 } 788 789 if (mDecorContentParent != null) { 790 // Enforce ordering guarantees around events so that the action bar never 791 // dispatches menu-related events before the panel is prepared. 792 mDecorContentParent.setMenuPrepared(); 793 } 794 795 // Init the panel state's menu--return false if init failed 796 if (mMenu == null || mPanelRefreshMenuContent) { 797 if (mMenu == null) { 798 if (!initializePanelMenu() || (mMenu == null)) { 799 return false; 800 } 801 } 802 803 if (mDecorContentParent != null) { 804 if (mActionMenuPresenterCallback == null) { 805 mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); 806 } 807 mDecorContentParent.setMenu(mMenu, mActionMenuPresenterCallback); 808 } 809 810 // Creating the panel menu will involve a lot of manipulation; 811 // don't dispatch change events to presenters until we're done. 812 mMenu.stopDispatchingItemsChanged(); 813 814 // Call callback, and return if it doesn't want to display menu. 815 if (!getWindowCallback().onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, mMenu)) { 816 // Ditch the menu created above 817 mMenu = null; 818 819 if (mDecorContentParent != null) { 820 // Don't show it in the action bar either 821 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 822 } 823 824 return false; 825 } 826 827 mPanelRefreshMenuContent = false; 828 } 829 830 // Preparing the panel menu can involve a lot of manipulation; 831 // don't dispatch change events to presenters until we're done. 832 mMenu.stopDispatchingItemsChanged(); 833 834 // Restore action view state before we prepare. This gives apps 835 // an opportunity to override frozen/restored state in onPrepare. 836 if (mPanelFrozenActionViewState != null) { 837 mMenu.restoreActionViewStates(mPanelFrozenActionViewState); 838 mPanelFrozenActionViewState = null; 839 } 840 841 // Callback and return if the callback does not want to show the menu 842 if (!getWindowCallback().onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, mMenu)) { 843 if (mDecorContentParent != null) { 844 // The app didn't want to show the menu for now but it still exists. 845 // Clear it out of the action bar. 846 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 847 } 848 mMenu.startDispatchingItemsChanged(); 849 return false; 850 } 851 852 mMenu.startDispatchingItemsChanged(); 853 854 // Set other state 855 mPanelIsPrepared = true; 856 857 return true; 858 } 859 860 private void checkCloseActionMenu() { 861 if (mClosingActionMenu) { 862 return; 863 } 864 865 mClosingActionMenu = true; 866 mDecorContentParent.dismissPopups(); 867 mClosingActionMenu = false; 868 } 869 870 private void closePanel(int featureId) { 871 if (featureId == Window.FEATURE_OPTIONS_PANEL && mDecorContentParent != null && 872 mDecorContentParent.canShowOverflowMenu() && 873 !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity))) { 874 mDecorContentParent.hideOverflowMenu(); 875 } else { 876 mActivity.closeOptionsMenu(); 877 mPanelIsPrepared = false; 878 } 879 } 880 881 private void invalidatePanelMenu() { 882 if (!mInvalidatePanelMenuPosted && mWindowDecor != null) { 883 ViewCompat.postOnAnimation(mWindowDecor, mInvalidatePanelMenuRunnable); 884 mInvalidatePanelMenuPosted = true; 885 } 886 } 887 888 final boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 889 if (event.isSystem()) { 890 return false; 891 } 892 893 boolean handled = false; 894 895 // Only try to perform menu shortcuts if preparePanel returned true (possible false 896 // return value from application not wanting to show the menu). 897 if ((mPanelIsPrepared || preparePanel()) && mMenu != null) { 898 // The menu is prepared now, perform the shortcut on it 899 handled = mMenu.performShortcut(keyCode, event, flags); 900 } 901 902 if (handled) { 903 // Only close down the menu if we don't have an action bar keeping it open. 904 if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) { 905 closePanel(featureId); 906 } 907 } 908 909 return handled; 910 } 911 912 /** 913 * Clears out internal reference when the action mode is destroyed. 914 */ 915 private class ActionModeCallbackWrapper implements ActionMode.Callback { 916 private ActionMode.Callback mWrapped; 917 918 public ActionModeCallbackWrapper(ActionMode.Callback wrapped) { 919 mWrapped = wrapped; 920 } 921 922 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 923 return mWrapped.onCreateActionMode(mode, menu); 924 } 925 926 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 927 return mWrapped.onPrepareActionMode(mode, menu); 928 } 929 930 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 931 return mWrapped.onActionItemClicked(mode, item); 932 } 933 934 public void onDestroyActionMode(ActionMode mode) { 935 mWrapped.onDestroyActionMode(mode); 936 if (mActionModePopup != null) { 937 mActivity.getWindow().getDecorView().removeCallbacks(mShowActionModePopup); 938 mActionModePopup.dismiss(); 939 } else if (mActionModeView != null) { 940 mActionModeView.setVisibility(View.GONE); 941 } 942 if (mActionModeView != null) { 943 mActionModeView.removeAllViews(); 944 } 945 if (mActivity != null) { 946 try { 947 mActivity.onSupportActionModeFinished(mActionMode); 948 } catch (AbstractMethodError ame) { 949 // Older apps might not implement this callback method. 950 } 951 } 952 mActionMode = null; 953 } 954 } 955 956 private final class PanelMenuPresenterCallback implements MenuPresenter.Callback { 957 @Override 958 public boolean onOpenSubMenu(MenuBuilder subMenu) { 959 return false; 960 } 961 962 @Override 963 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 964 closePanel(Window.FEATURE_OPTIONS_PANEL); 965 } 966 } 967 968 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { 969 @Override 970 public boolean onOpenSubMenu(MenuBuilder subMenu) { 971 return false; 972 } 973 974 @Override 975 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 976 checkCloseActionMenu(); 977 } 978 } 979 980} 981