ActionBarActivityDelegateBase.java revision c132781c9ed6354e451c647737013600b0da2425
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.graphics.Rect; 24import android.os.Build; 25import android.os.Bundle; 26import android.os.Parcel; 27import android.os.Parcelable; 28import android.support.annotation.NonNull; 29import android.support.v4.app.NavUtils; 30import android.support.v4.view.OnApplyWindowInsetsListener; 31import android.support.v4.view.ViewCompat; 32import android.support.v4.view.ViewConfigurationCompat; 33import android.support.v4.view.WindowInsetsCompat; 34import android.support.v7.appcompat.R; 35import android.support.v7.internal.app.ToolbarActionBar; 36import android.support.v7.internal.app.WindowCallback; 37import android.support.v7.internal.app.WindowDecorActionBar; 38import android.support.v7.internal.view.StandaloneActionMode; 39import android.support.v7.internal.view.menu.ListMenuPresenter; 40import android.support.v7.internal.view.menu.MenuBuilder; 41import android.support.v7.internal.view.menu.MenuPresenter; 42import android.support.v7.internal.view.menu.MenuView; 43import android.support.v7.internal.widget.ActionBarContextView; 44import android.support.v7.internal.widget.DecorContentParent; 45import android.support.v7.internal.widget.FitWindowsViewGroup; 46import android.support.v7.internal.widget.TintAutoCompleteTextView; 47import android.support.v7.internal.widget.TintCheckBox; 48import android.support.v7.internal.widget.TintCheckedTextView; 49import android.support.v7.internal.widget.TintEditText; 50import android.support.v7.internal.widget.TintMultiAutoCompleteTextView; 51import android.support.v7.internal.widget.TintRadioButton; 52import android.support.v7.internal.widget.TintSpinner; 53import android.support.v7.internal.widget.ViewStubCompat; 54import android.support.v7.internal.widget.ViewUtils; 55import android.support.v7.view.ActionMode; 56import android.support.v7.widget.Toolbar; 57import android.util.AttributeSet; 58import android.util.DisplayMetrics; 59import android.util.TypedValue; 60import android.view.ContextThemeWrapper; 61import android.view.Gravity; 62import android.view.KeyCharacterMap; 63import android.view.KeyEvent; 64import android.view.LayoutInflater; 65import android.view.Menu; 66import android.view.MenuItem; 67import android.view.View; 68import android.view.ViewConfiguration; 69import android.view.ViewGroup; 70import android.view.Window; 71import android.view.accessibility.AccessibilityEvent; 72import android.widget.FrameLayout; 73import android.widget.PopupWindow; 74 75import static android.support.v4.view.WindowCompat.FEATURE_ACTION_BAR; 76import static android.support.v4.view.WindowCompat.FEATURE_ACTION_BAR_OVERLAY; 77import static android.support.v4.view.WindowCompat.FEATURE_ACTION_MODE_OVERLAY; 78import static android.view.Window.FEATURE_OPTIONS_PANEL; 79 80class ActionBarActivityDelegateBase extends ActionBarActivityDelegate 81 implements MenuBuilder.Callback { 82 private static final String TAG = "ActionBarActivityDelegateBase"; 83 84 private DecorContentParent mDecorContentParent; 85 private ActionMenuPresenterCallback mActionMenuPresenterCallback; 86 private PanelMenuPresenterCallback mPanelMenuPresenterCallback; 87 88 ActionMode mActionMode; 89 ActionBarContextView mActionModeView; 90 PopupWindow mActionModePopup; 91 Runnable mShowActionModePopup; 92 93 // true if we have installed a window sub-decor layout. 94 private boolean mSubDecorInstalled; 95 private ViewGroup mWindowDecor; 96 private ViewGroup mSubDecor; 97 98 private View mStatusGuard; 99 100 private CharSequence mTitleToSet; 101 102 // Used to keep track of Progress Bar Window features 103 private boolean mFeatureProgress, mFeatureIndeterminateProgress; 104 105 // Used for emulating PanelFeatureState 106 private boolean mClosingActionMenu; 107 private PanelFeatureState[] mPanels; 108 private PanelFeatureState mPreparedPanel; 109 110 private boolean mInvalidatePanelMenuPosted; 111 private int mInvalidatePanelMenuFeatures; 112 private final Runnable mInvalidatePanelMenuRunnable = new Runnable() { 113 @Override 114 public void run() { 115 if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_OPTIONS_PANEL) != 0) { 116 doInvalidatePanelMenu(FEATURE_OPTIONS_PANEL); 117 } 118 if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_ACTION_BAR) != 0) { 119 doInvalidatePanelMenu(FEATURE_ACTION_BAR); 120 } 121 mInvalidatePanelMenuPosted = false; 122 mInvalidatePanelMenuFeatures = 0; 123 } 124 }; 125 126 private boolean mEnableDefaultActionBarUp; 127 128 private ListMenuPresenter mToolbarListMenuPresenter; 129 130 private Rect mTempRect1; 131 private Rect mTempRect2; 132 133 ActionBarActivityDelegateBase(ActionBarActivity activity) { 134 super(activity); 135 } 136 137 @Override 138 void onCreate(Bundle savedInstanceState) { 139 super.onCreate(savedInstanceState); 140 141 mWindowDecor = (ViewGroup) mActivity.getWindow().getDecorView(); 142 143 if (NavUtils.getParentActivityName(mActivity) != null) { 144 ActionBar ab = getSupportActionBar(); 145 if (ab == null) { 146 mEnableDefaultActionBarUp = true; 147 } else { 148 ab.setDefaultDisplayHomeAsUpEnabled(true); 149 } 150 } 151 } 152 153 @Override 154 public ActionBar createSupportActionBar() { 155 ensureSubDecor(); 156 ActionBar ab = new WindowDecorActionBar(mActivity, mOverlayActionBar); 157 ab.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); 158 return ab; 159 } 160 161 @Override 162 void setSupportActionBar(Toolbar toolbar) { 163 final ActionBar ab = getSupportActionBar(); 164 if (ab instanceof WindowDecorActionBar) { 165 throw new IllegalStateException("This Activity already has an action bar supplied " + 166 "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " + 167 "windowActionBar to false in your theme to use a Toolbar instead."); 168 } else if (ab instanceof ToolbarActionBar) { 169 // Make sure we reset the old toolbar AB's list menu presenter 170 ((ToolbarActionBar) ab).setListMenuPresenter(null); 171 } 172 173 // Need to make sure we give the action bar the default window callback. Otherwise multiple 174 // setSupportActionBar() calls lead to memory leaks 175 ToolbarActionBar tbab = new ToolbarActionBar(toolbar, mActivity.getTitle(), 176 mActivity.getWindow(), mDefaultWindowCallback); 177 ensureToolbarListMenuPresenter(); 178 tbab.setListMenuPresenter(mToolbarListMenuPresenter); 179 setSupportActionBar(tbab); 180 setWindowCallback(tbab.getWrappedWindowCallback()); 181 tbab.invalidateOptionsMenu(); 182 } 183 184 @Override 185 public void onConfigurationChanged(Configuration newConfig) { 186 // If this is called before sub-decor is installed, ActionBar will not 187 // be properly initialized. 188 if (mHasActionBar && mSubDecorInstalled) { 189 // Note: The action bar will need to access 190 // view changes from superclass. 191 ActionBar ab = getSupportActionBar(); 192 if (ab != null) { 193 ab.onConfigurationChanged(newConfig); 194 } 195 } 196 } 197 198 @Override 199 public void onStop() { 200 ActionBar ab = getSupportActionBar(); 201 if (ab != null) { 202 ab.setShowHideAnimationEnabled(false); 203 } 204 } 205 206 @Override 207 public void onPostResume() { 208 ActionBar ab = getSupportActionBar(); 209 if (ab != null) { 210 ab.setShowHideAnimationEnabled(true); 211 } 212 } 213 214 @Override 215 public void setContentView(View v) { 216 ensureSubDecor(); 217 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 218 contentParent.removeAllViews(); 219 contentParent.addView(v); 220 mActivity.onSupportContentChanged(); 221 } 222 223 @Override 224 public void setContentView(int resId) { 225 ensureSubDecor(); 226 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 227 contentParent.removeAllViews(); 228 mActivity.getLayoutInflater().inflate(resId, contentParent); 229 mActivity.onSupportContentChanged(); 230 } 231 232 @Override 233 public void setContentView(View v, ViewGroup.LayoutParams lp) { 234 ensureSubDecor(); 235 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 236 contentParent.removeAllViews(); 237 contentParent.addView(v, lp); 238 mActivity.onSupportContentChanged(); 239 } 240 241 @Override 242 public void addContentView(View v, ViewGroup.LayoutParams lp) { 243 ensureSubDecor(); 244 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 245 contentParent.addView(v, lp); 246 mActivity.onSupportContentChanged(); 247 } 248 249 @Override 250 public void onContentChanged() { 251 // Ignore all calls to this method as we call onSupportContentChanged manually above 252 } 253 254 final void ensureSubDecor() { 255 if (!mSubDecorInstalled) { 256 if (mHasActionBar) { 257 /** 258 * This needs some explanation. As we can not use the android:theme attribute 259 * pre-L, we emulate it by manually creating a LayoutInflater using a 260 * ContextThemeWrapper pointing to actionBarTheme. 261 */ 262 TypedValue outValue = new TypedValue(); 263 mActivity.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true); 264 265 Context themedContext; 266 if (outValue.resourceId != 0) { 267 themedContext = new ContextThemeWrapper(mActivity, outValue.resourceId); 268 } else { 269 themedContext = mActivity; 270 } 271 272 // Now inflate the view using the themed context and set it as the content view 273 mSubDecor = (ViewGroup) LayoutInflater.from(themedContext) 274 .inflate(R.layout.abc_screen_toolbar, null); 275 276 mDecorContentParent = (DecorContentParent) mSubDecor 277 .findViewById(R.id.decor_content_parent); 278 mDecorContentParent.setWindowCallback(getWindowCallback()); 279 280 /** 281 * Propagate features to DecorContentParent 282 */ 283 if (mOverlayActionBar) { 284 mDecorContentParent.initFeature(FEATURE_ACTION_BAR_OVERLAY); 285 } 286 if (mFeatureProgress) { 287 mDecorContentParent.initFeature(Window.FEATURE_PROGRESS); 288 } 289 if (mFeatureIndeterminateProgress) { 290 mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 291 } 292 } else { 293 if (mOverlayActionMode) { 294 mSubDecor = (ViewGroup) LayoutInflater.from(mActivity) 295 .inflate(R.layout.abc_screen_simple_overlay_action_mode, null); 296 } else { 297 mSubDecor = (ViewGroup) LayoutInflater.from(mActivity) 298 .inflate(R.layout.abc_screen_simple, null); 299 } 300 301 if (Build.VERSION.SDK_INT >= 21) { 302 // If we're running on L or above, we can rely on ViewCompat's 303 // setOnApplyWindowInsetsListener 304 ViewCompat.setOnApplyWindowInsetsListener(mSubDecor, 305 new OnApplyWindowInsetsListener() { 306 @Override 307 public WindowInsetsCompat onApplyWindowInsets(View v, 308 WindowInsetsCompat insets) { 309 final int top = insets.getSystemWindowInsetTop(); 310 final int newTop = updateStatusGuard(top); 311 312 if (top != newTop) { 313 return insets.replaceSystemWindowInsets( 314 insets.getSystemWindowInsetLeft(), 315 newTop, 316 insets.getSystemWindowInsetRight(), 317 insets.getSystemWindowInsetBottom()); 318 } else { 319 return insets; 320 } 321 } 322 }); 323 } else { 324 // Else, we need to use our own FitWindowsViewGroup handling 325 ((FitWindowsViewGroup) mSubDecor).setOnFitSystemWindowsListener( 326 new FitWindowsViewGroup.OnFitSystemWindowsListener() { 327 @Override 328 public void onFitSystemWindows(Rect insets) { 329 insets.top = updateStatusGuard(insets.top); 330 } 331 }); 332 } 333 } 334 335 // Make the decor optionally fit system windows, like the window's decor 336 ViewUtils.makeOptionalFitsSystemWindows(mSubDecor); 337 338 // Now set the Activity's content view with the decor 339 mActivity.superSetContentView(mSubDecor); 340 341 // Change our content FrameLayout to use the android.R.id.content id. 342 // Useful for fragments. 343 final View decorContent = mActivity.findViewById(android.R.id.content); 344 decorContent.setId(View.NO_ID); 345 View abcContent = mActivity.findViewById(R.id.action_bar_activity_content); 346 abcContent.setId(android.R.id.content); 347 348 // The decorContent may have a foreground drawable set (windowContentOverlay). 349 // Remove this as we handle it ourselves 350 if (decorContent instanceof FrameLayout) { 351 ((FrameLayout) decorContent).setForeground(null); 352 } 353 354 // A title was set before we've install the decor so set it now. 355 if (mTitleToSet != null && mDecorContentParent != null) { 356 mDecorContentParent.setWindowTitle(mTitleToSet); 357 mTitleToSet = null; 358 } 359 360 applyFixedSizeWindow(); 361 362 onSubDecorInstalled(); 363 364 mSubDecorInstalled = true; 365 366 // Invalidate if the panel menu hasn't been created before this. 367 // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu 368 // being called in the middle of onCreate or similar. 369 // A pending invalidation will typically be resolved before the posted message 370 // would run normally in order to satisfy instance state restoration. 371 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 372 if (!isDestroyed() && (st == null || st.menu == null)) { 373 invalidatePanelMenu(FEATURE_ACTION_BAR); 374 } 375 } 376 } 377 378 void onSubDecorInstalled() {} 379 380 private void applyFixedSizeWindow() { 381 TypedArray a = mActivity.obtainStyledAttributes(R.styleable.Theme); 382 383 TypedValue mFixedWidthMajor = null; 384 TypedValue mFixedWidthMinor = null; 385 TypedValue mFixedHeightMajor = null; 386 TypedValue mFixedHeightMinor = null; 387 388 if (a.hasValue(R.styleable.Theme_windowFixedWidthMajor)) { 389 if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue(); 390 a.getValue(R.styleable.Theme_windowFixedWidthMajor, mFixedWidthMajor); 391 } 392 if (a.hasValue(R.styleable.Theme_windowFixedWidthMinor)) { 393 if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue(); 394 a.getValue(R.styleable.Theme_windowFixedWidthMinor, mFixedWidthMinor); 395 } 396 if (a.hasValue(R.styleable.Theme_windowFixedHeightMajor)) { 397 if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue(); 398 a.getValue(R.styleable.Theme_windowFixedHeightMajor, mFixedHeightMajor); 399 } 400 if (a.hasValue(R.styleable.Theme_windowFixedHeightMinor)) { 401 if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue(); 402 a.getValue(R.styleable.Theme_windowFixedHeightMinor, mFixedHeightMinor); 403 } 404 405 final DisplayMetrics metrics = mActivity.getResources().getDisplayMetrics(); 406 final boolean isPortrait = metrics.widthPixels < metrics.heightPixels; 407 int w = ViewGroup.LayoutParams.MATCH_PARENT; 408 int h = ViewGroup.LayoutParams.MATCH_PARENT; 409 410 final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor; 411 if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { 412 if (tvw.type == TypedValue.TYPE_DIMENSION) { 413 w = (int) tvw.getDimension(metrics); 414 } else if (tvw.type == TypedValue.TYPE_FRACTION) { 415 w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels); 416 } 417 } 418 419 final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor; 420 if (tvh != null && tvh.type != TypedValue.TYPE_NULL) { 421 if (tvh.type == TypedValue.TYPE_DIMENSION) { 422 h = (int) tvh.getDimension(metrics); 423 } else if (tvh.type == TypedValue.TYPE_FRACTION) { 424 h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels); 425 } 426 } 427 428 if (w != ViewGroup.LayoutParams.MATCH_PARENT || h != ViewGroup.LayoutParams.MATCH_PARENT) { 429 mActivity.getWindow().setLayout(w, h); 430 } 431 432 a.recycle(); 433 } 434 435 @Override 436 public boolean supportRequestWindowFeature(int featureId) { 437 switch (featureId) { 438 case FEATURE_ACTION_BAR: 439 mHasActionBar = true; 440 return true; 441 case FEATURE_ACTION_BAR_OVERLAY: 442 mOverlayActionBar = true; 443 return true; 444 case FEATURE_ACTION_MODE_OVERLAY: 445 mOverlayActionMode = true; 446 return true; 447 case Window.FEATURE_PROGRESS: 448 mFeatureProgress = true; 449 return true; 450 case Window.FEATURE_INDETERMINATE_PROGRESS: 451 mFeatureIndeterminateProgress = true; 452 return true; 453 default: 454 return mActivity.requestWindowFeature(featureId); 455 } 456 } 457 458 @Override 459 public void onTitleChanged(CharSequence title) { 460 if (mDecorContentParent != null) { 461 mDecorContentParent.setWindowTitle(title); 462 } else if (getSupportActionBar() != null) { 463 getSupportActionBar().setWindowTitle(title); 464 } else { 465 mTitleToSet = title; 466 } 467 } 468 469 @Override 470 public View onCreatePanelView(int featureId) { 471 View panelView = null; 472 473 // If there isn't an action mode currently being displayed 474 if (mActionMode == null) { 475 // Let our window callback try first 476 WindowCallback callback = getWindowCallback(); 477 if (callback != null) { 478 panelView = callback.onCreatePanelView(featureId); 479 } 480 481 if (panelView == null && mToolbarListMenuPresenter == null) { 482 // Only check our panels if the callback didn't return a view and we do not have 483 // a ListMenuPresenter for Toolbars. We check for the ListMenuPresenter because 484 // once created, Toolbar needs to control the panel view regardless of whether it 485 // has any non-action items to display. 486 PanelFeatureState st = getPanelState(featureId, true); 487 openPanel(st, null); 488 if (st.isOpen) { 489 panelView = st.shownPanelView; 490 } 491 } 492 } 493 return panelView; 494 } 495 496 @Override 497 public boolean onCreatePanelMenu(int featureId, Menu menu) { 498 if (featureId != Window.FEATURE_OPTIONS_PANEL) { 499 return getWindowCallback().onCreatePanelMenu(featureId, menu); 500 } 501 return false; 502 } 503 504 @Override 505 public boolean onPreparePanel(int featureId, View view, Menu menu) { 506 if (featureId != Window.FEATURE_OPTIONS_PANEL) { 507 return getWindowCallback().onPreparePanel(featureId, view, menu); 508 } 509 return false; 510 } 511 512 @Override 513 public void onPanelClosed(final int featureId, Menu menu) { 514 PanelFeatureState st = getPanelState(featureId, false); 515 if (st != null) { 516 // If we know about the feature id, update it's state 517 closePanel(st, false); 518 } 519 520 if (featureId == FEATURE_ACTION_BAR) { 521 ActionBar ab = getSupportActionBar(); 522 if (ab != null) { 523 ab.dispatchMenuVisibilityChanged(false); 524 } 525 } else if (!isDestroyed()) { 526 // Only pass it through to the Activity's super impl if it's not ACTION_BAR. This is 527 // because ICS+ will try and create a framework action bar due to this call 528 mActivity.superOnPanelClosed(featureId, menu); 529 } 530 } 531 532 @Override 533 boolean onMenuOpened(final int featureId, Menu menu) { 534 if (featureId == FEATURE_ACTION_BAR) { 535 ActionBar ab = getSupportActionBar(); 536 if (ab != null) { 537 ab.dispatchMenuVisibilityChanged(true); 538 } 539 return true; 540 } else { 541 return mActivity.superOnMenuOpened(featureId, menu); 542 } 543 } 544 545 @Override 546 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 547 final WindowCallback cb = getWindowCallback(); 548 if (cb != null && !isDestroyed()) { 549 final PanelFeatureState panel = findMenuPanel(menu.getRootMenu()); 550 if (panel != null) { 551 return cb.onMenuItemSelected(panel.featureId, item); 552 } 553 } 554 return false; 555 } 556 557 @Override 558 public void onMenuModeChange(MenuBuilder menu) { 559 reopenMenu(menu, true); 560 } 561 562 @Override 563 public ActionMode startSupportActionMode(ActionMode.Callback callback) { 564 if (callback == null) { 565 throw new IllegalArgumentException("ActionMode callback can not be null."); 566 } 567 568 if (mActionMode != null) { 569 mActionMode.finish(); 570 } 571 572 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); 573 574 ActionBar ab = getSupportActionBar(); 575 if (ab != null) { 576 mActionMode = ab.startActionMode(wrappedCallback); 577 if (mActionMode != null) { 578 mActivity.onSupportActionModeStarted(mActionMode); 579 } 580 } 581 582 if (mActionMode == null) { 583 // If the action bar didn't provide an action mode, start the emulated window one 584 mActionMode = startSupportActionModeFromWindow(wrappedCallback); 585 } 586 587 return mActionMode; 588 } 589 590 @Override 591 public void supportInvalidateOptionsMenu() { 592 final ActionBar ab = getSupportActionBar(); 593 if (ab != null && ab.invalidateOptionsMenu()) return; 594 595 invalidatePanelMenu(FEATURE_OPTIONS_PANEL); 596 } 597 598 @Override 599 ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback) { 600 if (mActionMode != null) { 601 mActionMode.finish(); 602 } 603 604 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); 605 final Context context = getActionBarThemedContext(); 606 607 if (mActionModeView == null) { 608 if (mIsFloating) { 609 mActionModeView = new ActionBarContextView(context); 610 mActionModePopup = new PopupWindow(context, null, 611 R.attr.actionModePopupWindowStyle); 612 mActionModePopup.setContentView(mActionModeView); 613 mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); 614 615 TypedValue heightValue = new TypedValue(); 616 mActivity.getTheme().resolveAttribute(R.attr.actionBarSize, heightValue, true); 617 final int height = TypedValue.complexToDimensionPixelSize(heightValue.data, 618 mActivity.getResources().getDisplayMetrics()); 619 mActionModeView.setContentHeight(height); 620 mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); 621 mShowActionModePopup = new Runnable() { 622 public void run() { 623 mActionModePopup.showAtLocation( 624 mActionModeView, 625 Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); 626 } 627 }; 628 } else { 629 ViewStubCompat stub = (ViewStubCompat) mActivity 630 .findViewById(R.id.action_mode_bar_stub); 631 if (stub != null) { 632 // Set the layout inflater so that it is inflated with the action bar's context 633 stub.setLayoutInflater(LayoutInflater.from(context)); 634 mActionModeView = (ActionBarContextView) stub.inflate(); 635 } 636 } 637 } 638 639 if (mActionModeView != null) { 640 mActionModeView.killMode(); 641 ActionMode mode = new StandaloneActionMode(context, mActionModeView, wrappedCallback, 642 mActionModePopup == null); 643 if (callback.onCreateActionMode(mode, mode.getMenu())) { 644 mode.invalidate(); 645 mActionModeView.initForMode(mode); 646 mActionModeView.setVisibility(View.VISIBLE); 647 mActionMode = mode; 648 if (mActionModePopup != null) { 649 mActivity.getWindow().getDecorView().post(mShowActionModePopup); 650 } 651 mActionModeView.sendAccessibilityEvent( 652 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 653 654 if (mActionModeView.getParent() != null) { 655 ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); 656 } 657 } else { 658 mActionMode = null; 659 } 660 } 661 if (mActionMode != null && mActivity != null) { 662 mActivity.onSupportActionModeStarted(mActionMode); 663 } 664 return mActionMode; 665 } 666 667 @Override 668 public boolean onBackPressed() { 669 // Back cancels action modes first. 670 if (mActionMode != null) { 671 mActionMode.finish(); 672 return true; 673 } 674 675 // Next collapse any expanded action views. 676 ActionBar ab = getSupportActionBar(); 677 if (ab != null && ab.collapseActionView()) { 678 return true; 679 } 680 681 return false; 682 } 683 684 @Override 685 void setSupportProgressBarVisibility(boolean visible) { 686 // noop 687 } 688 689 @Override 690 void setSupportProgressBarIndeterminateVisibility(boolean visible) { 691 // noop 692 } 693 694 @Override 695 void setSupportProgressBarIndeterminate(boolean indeterminate) { 696 // noop 697 } 698 699 @Override 700 void setSupportProgress(int progress) { 701 // noop 702 } 703 704 @Override 705 int getHomeAsUpIndicatorAttrId() { 706 return R.attr.homeAsUpIndicator; 707 } 708 709 @Override 710 boolean onKeyShortcut(int keyCode, KeyEvent ev) { 711 // If the panel is already prepared, then perform the shortcut using it. 712 boolean handled; 713 if (mPreparedPanel != null) { 714 handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev, 715 Menu.FLAG_PERFORM_NO_CLOSE); 716 if (handled) { 717 if (mPreparedPanel != null) { 718 mPreparedPanel.isHandled = true; 719 } 720 return true; 721 } 722 } 723 724 // If the panel is not prepared, then we may be trying to handle a shortcut key 725 // combination such as Control+C. Temporarily prepare the panel then mark it 726 // unprepared again when finished to ensure that the panel will again be prepared 727 // the next time it is shown for real. 728 if (mPreparedPanel == null) { 729 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 730 preparePanel(st, ev); 731 handled = performPanelShortcut(st, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE); 732 st.isPrepared = false; 733 if (handled) { 734 return true; 735 } 736 } 737 return false; 738 } 739 740 @Override 741 boolean onKeyDown(int keyCode, KeyEvent event) { 742 // On API v7-10 we need to manually call onKeyShortcut() as this is not called 743 // from the Activity 744 return onKeyShortcut(keyCode, event); 745 } 746 747 @Override 748 View createView(final String name, @NonNull AttributeSet attrs) { 749 if (Build.VERSION.SDK_INT < 21) { 750 // If we're running pre-L, we need to 'inject' our tint aware Views in place of the 751 // standard framework versions 752 switch (name) { 753 case "EditText": 754 return new TintEditText(mActivity, attrs); 755 case "Spinner": 756 return new TintSpinner(mActivity, attrs); 757 case "CheckBox": 758 return new TintCheckBox(mActivity, attrs); 759 case "RadioButton": 760 return new TintRadioButton(mActivity, attrs); 761 case "CheckedTextView": 762 return new TintCheckedTextView(mActivity, attrs); 763 case "AutoCompleteTextView": 764 return new TintAutoCompleteTextView(mActivity, attrs); 765 case "MultiAutoCompleteTextView": 766 return new TintMultiAutoCompleteTextView(mActivity, attrs); 767 } 768 } 769 return null; 770 } 771 772 private void openPanel(int featureId, KeyEvent event) { 773 if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null && 774 mDecorContentParent.canShowOverflowMenu() && 775 !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity))) { 776 mDecorContentParent.showOverflowMenu(); 777 } else { 778 openPanel(getPanelState(featureId, true), event); 779 } 780 } 781 782 private void openPanel(final PanelFeatureState st, KeyEvent event) { 783 // Already open, return 784 if (st.isOpen || isDestroyed()) { 785 return; 786 } 787 788 // Don't open an options panel for honeycomb apps on xlarge devices. 789 // (The app should be using an action bar for menu items.) 790 if (st.featureId == FEATURE_OPTIONS_PANEL) { 791 Context context = mActivity; 792 Configuration config = context.getResources().getConfiguration(); 793 boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == 794 Configuration.SCREENLAYOUT_SIZE_XLARGE; 795 boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >= 796 android.os.Build.VERSION_CODES.HONEYCOMB; 797 798 if (isXLarge && isHoneycombApp) { 799 return; 800 } 801 } 802 803 WindowCallback cb = getWindowCallback(); 804 if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) { 805 // Callback doesn't want the menu to open, reset any state 806 closePanel(st, true); 807 return; 808 } 809 810 // Prepare panel (should have been done before, but just in case) 811 if (!preparePanel(st, event)) { 812 return; 813 } 814 815 if (st.decorView == null || st.refreshDecorView) { 816 initializePanelDecor(st); 817 } 818 819 // This will populate st.shownPanelView 820 if (!initializePanelContent(st) || !st.hasPanelItems()) { 821 return; 822 } 823 824 st.isHandled = false; 825 st.isOpen = true; 826 } 827 828 private void initializePanelDecor(PanelFeatureState st) { 829 st.decorView = mWindowDecor; 830 st.setStyle(getActionBarThemedContext()); 831 } 832 833 private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) { 834 if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() && 835 (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity)) || 836 mDecorContentParent.isOverflowMenuShowPending())) { 837 838 WindowCallback cb = getWindowCallback(); 839 840 if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) { 841 if (cb != null && !isDestroyed()) { 842 // If we have a menu invalidation pending, do it now. 843 if (mInvalidatePanelMenuPosted && 844 (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) { 845 mWindowDecor.removeCallbacks(mInvalidatePanelMenuRunnable); 846 mInvalidatePanelMenuRunnable.run(); 847 } 848 849 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 850 851 // If we don't have a menu or we're waiting for a full content refresh, 852 // forget it. This is a lingering event that no longer matters. 853 if (st.menu != null && !st.refreshMenuContent && 854 cb.onPreparePanel(FEATURE_OPTIONS_PANEL, null, st.menu)) { 855 cb.onMenuOpened(FEATURE_ACTION_BAR, st.menu); 856 mDecorContentParent.showOverflowMenu(); 857 } 858 } 859 } else { 860 mDecorContentParent.hideOverflowMenu(); 861 if (!isDestroyed()) { 862 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 863 mActivity.onPanelClosed(FEATURE_ACTION_BAR, st.menu); 864 } 865 } 866 return; 867 } 868 869 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 870 871 st.refreshDecorView = true; 872 closePanel(st, false); 873 874 openPanel(st, null); 875 } 876 877 private boolean initializePanelMenu(final PanelFeatureState st) { 878 Context context = mActivity; 879 880 // If we have an action bar, initialize the menu with the right theme. 881 if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR) && 882 mDecorContentParent != null) { 883 final TypedValue outValue = new TypedValue(); 884 final Resources.Theme baseTheme = context.getTheme(); 885 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); 886 887 Resources.Theme widgetTheme = null; 888 if (outValue.resourceId != 0) { 889 widgetTheme = context.getResources().newTheme(); 890 widgetTheme.setTo(baseTheme); 891 widgetTheme.applyStyle(outValue.resourceId, true); 892 widgetTheme.resolveAttribute( 893 R.attr.actionBarWidgetTheme, outValue, true); 894 } else { 895 baseTheme.resolveAttribute( 896 R.attr.actionBarWidgetTheme, outValue, true); 897 } 898 899 if (outValue.resourceId != 0) { 900 if (widgetTheme == null) { 901 widgetTheme = context.getResources().newTheme(); 902 widgetTheme.setTo(baseTheme); 903 } 904 widgetTheme.applyStyle(outValue.resourceId, true); 905 } 906 907 if (widgetTheme != null) { 908 context = new ContextThemeWrapper(context, 0); 909 context.getTheme().setTo(widgetTheme); 910 } 911 } 912 913 final MenuBuilder menu = new MenuBuilder(context); 914 menu.setCallback(this); 915 st.setMenu(menu); 916 917 return true; 918 } 919 920 private boolean initializePanelContent(PanelFeatureState st) { 921 if (st.menu == null) { 922 return false; 923 } 924 925 if (mPanelMenuPresenterCallback == null) { 926 mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); 927 } 928 929 MenuView menuView = st.getListMenuView(mPanelMenuPresenterCallback); 930 931 st.shownPanelView = (View) menuView; 932 933 return st.shownPanelView != null; 934 } 935 936 private boolean preparePanel(PanelFeatureState st, KeyEvent event) { 937 if (isDestroyed()) { 938 return false; 939 } 940 941 // Already prepared (isPrepared will be reset to false later) 942 if (st.isPrepared) { 943 return true; 944 } 945 946 if ((mPreparedPanel != null) && (mPreparedPanel != st)) { 947 // Another Panel is prepared and possibly open, so close it 948 closePanel(mPreparedPanel, false); 949 } 950 951 final boolean isActionBarMenu = 952 (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR); 953 954 if (isActionBarMenu && mDecorContentParent != null) { 955 // Enforce ordering guarantees around events so that the action bar never 956 // dispatches menu-related events before the panel is prepared. 957 mDecorContentParent.setMenuPrepared(); 958 } 959 960 // Init the panel state's menu--return false if init failed 961 if (st.menu == null || st.refreshMenuContent) { 962 if (st.menu == null) { 963 if (!initializePanelMenu(st) || (st.menu == null)) { 964 return false; 965 } 966 } 967 968 if (isActionBarMenu && mDecorContentParent != null) { 969 if (mActionMenuPresenterCallback == null) { 970 mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); 971 } 972 mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback); 973 } 974 975 // Creating the panel menu will involve a lot of manipulation; 976 // don't dispatch change events to presenters until we're done. 977 st.menu.stopDispatchingItemsChanged(); 978 if (!getWindowCallback().onCreatePanelMenu(st.featureId, st.menu)) { 979 // Ditch the menu created above 980 st.setMenu(null); 981 982 if (isActionBarMenu && mDecorContentParent != null) { 983 // Don't show it in the action bar either 984 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 985 } 986 987 return false; 988 } 989 990 st.refreshMenuContent = false; 991 } 992 993 // Preparing the panel menu can involve a lot of manipulation; 994 // don't dispatch change events to presenters until we're done. 995 st.menu.stopDispatchingItemsChanged(); 996 997 // Restore action view state before we prepare. This gives apps 998 // an opportunity to override frozen/restored state in onPrepare. 999 if (st.frozenActionViewState != null) { 1000 st.menu.restoreActionViewStates(st.frozenActionViewState); 1001 st.frozenActionViewState = null; 1002 } 1003 1004 // Callback and return if the callback does not want to show the menu 1005 if (!getWindowCallback().onPreparePanel(FEATURE_OPTIONS_PANEL, null, st.menu)) { 1006 if (isActionBarMenu && mDecorContentParent != null) { 1007 // The app didn't want to show the menu for now but it still exists. 1008 // Clear it out of the action bar. 1009 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 1010 } 1011 st.menu.startDispatchingItemsChanged(); 1012 return false; 1013 } 1014 1015 // Set the proper keymap 1016 KeyCharacterMap kmap = KeyCharacterMap.load( 1017 event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD); 1018 st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC; 1019 st.menu.setQwertyMode(st.qwertyMode); 1020 st.menu.startDispatchingItemsChanged(); 1021 1022 // Set other state 1023 st.isPrepared = true; 1024 st.isHandled = false; 1025 mPreparedPanel = st; 1026 1027 return true; 1028 } 1029 1030 private void checkCloseActionMenu(MenuBuilder menu) { 1031 if (mClosingActionMenu) { 1032 return; 1033 } 1034 1035 mClosingActionMenu = true; 1036 mDecorContentParent.dismissPopups(); 1037 WindowCallback cb = getWindowCallback(); 1038 if (cb != null && !isDestroyed()) { 1039 cb.onPanelClosed(FEATURE_ACTION_BAR, menu); 1040 } 1041 mClosingActionMenu = false; 1042 } 1043 1044 private void closePanel(PanelFeatureState st, boolean doCallback) { 1045 if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL && 1046 mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) { 1047 checkCloseActionMenu(st.menu); 1048 return; 1049 } 1050 1051 if (st.isOpen) { 1052 if (doCallback) { 1053 callOnPanelClosed(st.featureId, st, null); 1054 } 1055 } 1056 1057 st.isPrepared = false; 1058 st.isHandled = false; 1059 st.isOpen = false; 1060 1061 // This view is no longer shown, so null it out 1062 st.shownPanelView = null; 1063 1064 // Next time the menu opens, it should not be in expanded mode, so 1065 // force a refresh of the decor 1066 st.refreshDecorView = true; 1067 1068 if (mPreparedPanel == st) { 1069 mPreparedPanel = null; 1070 } 1071 } 1072 1073 private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) { 1074 // Try to get a menu 1075 if (menu == null) { 1076 // Need a panel to grab the menu, so try to get that 1077 if (panel == null) { 1078 if ((featureId >= 0) && (featureId < mPanels.length)) { 1079 panel = mPanels[featureId]; 1080 } 1081 } 1082 1083 if (panel != null) { 1084 // menu still may be null, which is okay--we tried our best 1085 menu = panel.menu; 1086 } 1087 } 1088 1089 // If the panel is not open, do not callback 1090 if ((panel != null) && (!panel.isOpen)) 1091 return; 1092 1093 getWindowCallback().onPanelClosed(featureId, menu); 1094 } 1095 1096 private PanelFeatureState findMenuPanel(Menu menu) { 1097 final PanelFeatureState[] panels = mPanels; 1098 final int N = panels != null ? panels.length : 0; 1099 for (int i = 0; i < N; i++) { 1100 final PanelFeatureState panel = panels[i]; 1101 if (panel != null && panel.menu == menu) { 1102 return panel; 1103 } 1104 } 1105 return null; 1106 } 1107 1108 private PanelFeatureState getPanelState(int featureId, boolean required) { 1109 PanelFeatureState[] ar; 1110 if ((ar = mPanels) == null || ar.length <= featureId) { 1111 PanelFeatureState[] nar = new PanelFeatureState[featureId + 1]; 1112 if (ar != null) { 1113 System.arraycopy(ar, 0, nar, 0, ar.length); 1114 } 1115 mPanels = ar = nar; 1116 } 1117 1118 PanelFeatureState st = ar[featureId]; 1119 if (st == null) { 1120 ar[featureId] = st = new PanelFeatureState(featureId); 1121 } 1122 return st; 1123 } 1124 1125 final boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, 1126 int flags) { 1127 if (event.isSystem()) { 1128 return false; 1129 } 1130 1131 boolean handled = false; 1132 1133 // Only try to perform menu shortcuts if preparePanel returned true (possible false 1134 // return value from application not wanting to show the menu). 1135 if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) { 1136 // The menu is prepared now, perform the shortcut on it 1137 handled = st.menu.performShortcut(keyCode, event, flags); 1138 } 1139 1140 if (handled) { 1141 // Only close down the menu if we don't have an action bar keeping it open. 1142 if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) { 1143 closePanel(st, true); 1144 } 1145 } 1146 1147 return handled; 1148 } 1149 1150 private void invalidatePanelMenu(int featureId) { 1151 mInvalidatePanelMenuFeatures |= 1 << featureId; 1152 1153 if (!mInvalidatePanelMenuPosted && mWindowDecor != null) { 1154 ViewCompat.postOnAnimation(mWindowDecor, mInvalidatePanelMenuRunnable); 1155 mInvalidatePanelMenuPosted = true; 1156 } 1157 } 1158 1159 private void doInvalidatePanelMenu(int featureId) { 1160 PanelFeatureState st = getPanelState(featureId, true); 1161 Bundle savedActionViewStates = null; 1162 if (st.menu != null) { 1163 savedActionViewStates = new Bundle(); 1164 st.menu.saveActionViewStates(savedActionViewStates); 1165 if (savedActionViewStates.size() > 0) { 1166 st.frozenActionViewState = savedActionViewStates; 1167 } 1168 // This will be started again when the panel is prepared. 1169 st.menu.stopDispatchingItemsChanged(); 1170 st.menu.clear(); 1171 } 1172 st.refreshMenuContent = true; 1173 st.refreshDecorView = true; 1174 1175 // Prepare the options panel if we have an action bar 1176 if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL) 1177 && mDecorContentParent != null) { 1178 st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 1179 if (st != null) { 1180 st.isPrepared = false; 1181 preparePanel(st, null); 1182 } 1183 } 1184 } 1185 1186 /** 1187 * Updates the status bar guard 1188 * 1189 * @param insetTop the current top system window inset 1190 * @return the new top system window inset 1191 */ 1192 private int updateStatusGuard(int insetTop) { 1193 boolean showStatusGuard = false; 1194 // Show the status guard when the non-overlay contextual action bar is showing 1195 if (mActionModeView != null) { 1196 if (mActionModeView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 1197 ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) 1198 mActionModeView.getLayoutParams(); 1199 boolean mlpChanged = false; 1200 1201 if (mActionModeView.isShown()) { 1202 if (mTempRect1 == null) { 1203 mTempRect1 = new Rect(); 1204 mTempRect2 = new Rect(); 1205 } 1206 final Rect insets = mTempRect1; 1207 final Rect localInsets = mTempRect2; 1208 insets.set(0, insetTop, 0, 0); 1209 1210 ViewUtils.computeFitSystemWindows(mSubDecor, insets, localInsets); 1211 final int newMargin = localInsets.top == 0 ? insetTop : 0; 1212 if (mlp.topMargin != newMargin) { 1213 mlpChanged = true; 1214 mlp.topMargin = insetTop; 1215 1216 if (mStatusGuard == null) { 1217 mStatusGuard = new View(mActivity); 1218 mStatusGuard.setBackgroundColor(mActivity.getResources() 1219 .getColor(R.color.abc_input_method_navigation_guard)); 1220 mSubDecor.addView(mStatusGuard, -1, 1221 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1222 insetTop)); 1223 } else { 1224 ViewGroup.LayoutParams lp = mStatusGuard.getLayoutParams(); 1225 if (lp.height != insetTop) { 1226 lp.height = insetTop; 1227 mStatusGuard.setLayoutParams(lp); 1228 } 1229 } 1230 } 1231 1232 // The action mode's theme may differ from the app, so 1233 // always show the status guard above it. 1234 showStatusGuard = mStatusGuard != null; 1235 1236 // We only need to consume the insets if the action 1237 // mode is overlaid on the app content (e.g. it's 1238 // sitting in a FrameLayout, see 1239 // screen_simple_overlay_action_mode.xml). 1240 if (!mOverlayActionMode && showStatusGuard) { 1241 insetTop = 0; 1242 } 1243 } else { 1244 // reset top margin 1245 if (mlp.topMargin != 0) { 1246 mlpChanged = true; 1247 mlp.topMargin = 0; 1248 } 1249 } 1250 if (mlpChanged) { 1251 mActionModeView.setLayoutParams(mlp); 1252 } 1253 } 1254 } 1255 if (mStatusGuard != null) { 1256 mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE); 1257 } 1258 1259 return insetTop; 1260 } 1261 1262 private void ensureToolbarListMenuPresenter() { 1263 if (mToolbarListMenuPresenter == null) { 1264 // First resolve panelMenuListTheme 1265 TypedValue outValue = new TypedValue(); 1266 mActivity.getTheme().resolveAttribute(R.attr.panelMenuListTheme, outValue, true); 1267 1268 Context context = new ContextThemeWrapper(mActivity, 1269 outValue.resourceId != 0 1270 ? outValue.resourceId 1271 : R.style.Theme_AppCompat_CompactMenu); 1272 1273 mToolbarListMenuPresenter = new ListMenuPresenter(context, 1274 R.layout.abc_list_menu_item_layout); 1275 } 1276 } 1277 1278 /** 1279 * Clears out internal reference when the action mode is destroyed. 1280 */ 1281 private class ActionModeCallbackWrapper implements ActionMode.Callback { 1282 private ActionMode.Callback mWrapped; 1283 1284 public ActionModeCallbackWrapper(ActionMode.Callback wrapped) { 1285 mWrapped = wrapped; 1286 } 1287 1288 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 1289 return mWrapped.onCreateActionMode(mode, menu); 1290 } 1291 1292 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 1293 return mWrapped.onPrepareActionMode(mode, menu); 1294 } 1295 1296 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 1297 return mWrapped.onActionItemClicked(mode, item); 1298 } 1299 1300 public void onDestroyActionMode(ActionMode mode) { 1301 mWrapped.onDestroyActionMode(mode); 1302 if (mActionModePopup != null) { 1303 mActivity.getWindow().getDecorView().removeCallbacks(mShowActionModePopup); 1304 mActionModePopup.dismiss(); 1305 } else if (mActionModeView != null) { 1306 mActionModeView.setVisibility(View.GONE); 1307 if (mActionModeView.getParent() != null) { 1308 ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); 1309 } 1310 } 1311 if (mActionModeView != null) { 1312 mActionModeView.removeAllViews(); 1313 } 1314 if (mActivity != null) { 1315 try { 1316 mActivity.onSupportActionModeFinished(mActionMode); 1317 } catch (AbstractMethodError ame) { 1318 // Older apps might not implement this callback method. 1319 } 1320 } 1321 mActionMode = null; 1322 } 1323 } 1324 1325 private final class PanelMenuPresenterCallback implements MenuPresenter.Callback { 1326 @Override 1327 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1328 final Menu parentMenu = menu.getRootMenu(); 1329 final boolean isSubMenu = parentMenu != menu; 1330 final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu); 1331 if (panel != null) { 1332 if (isSubMenu) { 1333 callOnPanelClosed(panel.featureId, panel, parentMenu); 1334 closePanel(panel, true); 1335 } else { 1336 // Close the panel and only do the callback if the menu is being 1337 // closed completely, not if opening a sub menu 1338 mActivity.closeOptionsMenu(); 1339 closePanel(panel, allMenusAreClosing); 1340 } 1341 } 1342 } 1343 1344 @Override 1345 public boolean onOpenSubMenu(MenuBuilder subMenu) { 1346 if (subMenu == null && mHasActionBar) { 1347 WindowCallback cb = getWindowCallback(); 1348 if (cb != null && !isDestroyed()) { 1349 cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); 1350 } 1351 } 1352 return true; 1353 } 1354 } 1355 1356 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { 1357 @Override 1358 public boolean onOpenSubMenu(MenuBuilder subMenu) { 1359 WindowCallback cb = getWindowCallback(); 1360 if (cb != null) { 1361 cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu); 1362 } 1363 return true; 1364 } 1365 1366 @Override 1367 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1368 checkCloseActionMenu(menu); 1369 } 1370 } 1371 1372 private static final class PanelFeatureState { 1373 1374 /** Feature ID for this panel. */ 1375 int featureId; 1376 1377 /** Dynamic state of the panel. */ 1378 ViewGroup decorView; 1379 1380 /** The panel that we are actually showing. */ 1381 View shownPanelView; 1382 1383 /** Use {@link #setMenu} to set this. */ 1384 MenuBuilder menu; 1385 1386 ListMenuPresenter listMenuPresenter; 1387 1388 Context listPresenterContext; 1389 1390 /** 1391 * Whether the panel has been prepared (see 1392 * {@link #preparePanel}). 1393 */ 1394 boolean isPrepared; 1395 1396 /** 1397 * Whether an item's action has been performed. This happens in obvious 1398 * scenarios (user clicks on menu item), but can also happen with 1399 * chording menu+(shortcut key). 1400 */ 1401 boolean isHandled; 1402 1403 boolean isOpen; 1404 1405 public boolean qwertyMode; 1406 1407 boolean refreshDecorView; 1408 1409 boolean refreshMenuContent; 1410 1411 boolean wasLastOpen; 1412 1413 /** 1414 * Contains the state of the menu when told to freeze. 1415 */ 1416 Bundle frozenMenuState; 1417 1418 /** 1419 * Contains the state of associated action views when told to freeze. 1420 * These are saved across invalidations. 1421 */ 1422 Bundle frozenActionViewState; 1423 1424 PanelFeatureState(int featureId) { 1425 this.featureId = featureId; 1426 1427 refreshDecorView = false; 1428 } 1429 1430 public boolean hasPanelItems() { 1431 if (shownPanelView == null) return false; 1432 1433 return listMenuPresenter.getAdapter().getCount() > 0; 1434 } 1435 1436 /** 1437 * Unregister and free attached MenuPresenters. They will be recreated as needed. 1438 */ 1439 public void clearMenuPresenters() { 1440 if (menu != null) { 1441 menu.removeMenuPresenter(listMenuPresenter); 1442 } 1443 listMenuPresenter = null; 1444 } 1445 1446 void setStyle(Context context) { 1447 final TypedValue outValue = new TypedValue(); 1448 final Resources.Theme widgetTheme = context.getResources().newTheme(); 1449 widgetTheme.setTo(context.getTheme()); 1450 1451 // First apply the actionBarPopupTheme 1452 widgetTheme.resolveAttribute(R.attr.actionBarPopupTheme, outValue, true); 1453 if (outValue.resourceId != 0) { 1454 widgetTheme.applyStyle(outValue.resourceId, true); 1455 } 1456 1457 // Now apply the panelMenuListTheme 1458 widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true); 1459 if (outValue.resourceId != 0) { 1460 widgetTheme.applyStyle(outValue.resourceId, true); 1461 } else { 1462 widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true); 1463 } 1464 1465 context = new ContextThemeWrapper(context, 0); 1466 context.getTheme().setTo(widgetTheme); 1467 1468 listPresenterContext = context; 1469 } 1470 1471 void setMenu(MenuBuilder menu) { 1472 if (menu == this.menu) return; 1473 1474 if (this.menu != null) { 1475 this.menu.removeMenuPresenter(listMenuPresenter); 1476 } 1477 this.menu = menu; 1478 if (menu != null) { 1479 if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter); 1480 } 1481 } 1482 1483 MenuView getListMenuView(MenuPresenter.Callback cb) { 1484 if (menu == null) return null; 1485 1486 if (listMenuPresenter == null) { 1487 listMenuPresenter = new ListMenuPresenter(listPresenterContext, 1488 R.layout.abc_list_menu_item_layout); 1489 listMenuPresenter.setCallback(cb); 1490 menu.addMenuPresenter(listMenuPresenter); 1491 } 1492 1493 MenuView result = listMenuPresenter.getMenuView(decorView); 1494 1495 return result; 1496 } 1497 1498 Parcelable onSaveInstanceState() { 1499 SavedState savedState = new SavedState(); 1500 savedState.featureId = featureId; 1501 savedState.isOpen = isOpen; 1502 1503 if (menu != null) { 1504 savedState.menuState = new Bundle(); 1505 menu.savePresenterStates(savedState.menuState); 1506 } 1507 1508 return savedState; 1509 } 1510 1511 void onRestoreInstanceState(Parcelable state) { 1512 SavedState savedState = (SavedState) state; 1513 featureId = savedState.featureId; 1514 wasLastOpen = savedState.isOpen; 1515 frozenMenuState = savedState.menuState; 1516 1517 shownPanelView = null; 1518 decorView = null; 1519 } 1520 1521 void applyFrozenState() { 1522 if (menu != null && frozenMenuState != null) { 1523 menu.restorePresenterStates(frozenMenuState); 1524 frozenMenuState = null; 1525 } 1526 } 1527 1528 private static class SavedState implements Parcelable { 1529 int featureId; 1530 boolean isOpen; 1531 Bundle menuState; 1532 1533 public int describeContents() { 1534 return 0; 1535 } 1536 1537 public void writeToParcel(Parcel dest, int flags) { 1538 dest.writeInt(featureId); 1539 dest.writeInt(isOpen ? 1 : 0); 1540 1541 if (isOpen) { 1542 dest.writeBundle(menuState); 1543 } 1544 } 1545 1546 private static SavedState readFromParcel(Parcel source) { 1547 SavedState savedState = new SavedState(); 1548 savedState.featureId = source.readInt(); 1549 savedState.isOpen = source.readInt() == 1; 1550 1551 if (savedState.isOpen) { 1552 savedState.menuState = source.readBundle(); 1553 } 1554 1555 return savedState; 1556 } 1557 1558 public static final Parcelable.Creator<SavedState> CREATOR 1559 = new Parcelable.Creator<SavedState>() { 1560 public SavedState createFromParcel(Parcel in) { 1561 return readFromParcel(in); 1562 } 1563 1564 public SavedState[] newArray(int size) { 1565 return new SavedState[size]; 1566 } 1567 }; 1568 } 1569 } 1570 1571} 1572