AppCompatDelegateImplV7.java revision 913fa29fdd3a0c593a46f2c2d2d23b66810024f5
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.app.Activity; 20import android.app.Dialog; 21import android.content.Context; 22import android.content.res.Configuration; 23import android.content.res.Resources; 24import android.content.res.TypedArray; 25import android.graphics.PixelFormat; 26import android.graphics.Rect; 27import android.media.AudioManager; 28import android.os.Build; 29import android.os.Bundle; 30import android.os.Parcel; 31import android.os.Parcelable; 32import android.support.annotation.NonNull; 33import android.support.v4.app.NavUtils; 34import android.support.v4.os.ParcelableCompat; 35import android.support.v4.os.ParcelableCompatCreatorCallbacks; 36import android.support.v4.view.LayoutInflaterCompat; 37import android.support.v4.view.LayoutInflaterFactory; 38import android.support.v4.view.OnApplyWindowInsetsListener; 39import android.support.v4.view.ViewCompat; 40import android.support.v4.view.ViewConfigurationCompat; 41import android.support.v4.view.ViewPropertyAnimatorCompat; 42import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; 43import android.support.v4.view.WindowCompat; 44import android.support.v4.view.WindowInsetsCompat; 45import android.support.v4.widget.PopupWindowCompat; 46import android.support.v7.appcompat.R; 47import android.support.v7.view.ActionMode; 48import android.support.v7.view.ContextThemeWrapper; 49import android.support.v7.view.StandaloneActionMode; 50import android.support.v7.view.menu.ListMenuPresenter; 51import android.support.v7.view.menu.MenuBuilder; 52import android.support.v7.view.menu.MenuPresenter; 53import android.support.v7.view.menu.MenuView; 54import android.support.v7.widget.ActionBarContextView; 55import android.support.v7.widget.AppCompatDrawableManager; 56import android.support.v7.widget.ContentFrameLayout; 57import android.support.v7.widget.DecorContentParent; 58import android.support.v7.widget.FitWindowsViewGroup; 59import android.support.v7.widget.Toolbar; 60import android.support.v7.widget.ViewStubCompat; 61import android.support.v7.widget.ViewUtils; 62import android.text.TextUtils; 63import android.util.AndroidRuntimeException; 64import android.util.AttributeSet; 65import android.util.Log; 66import android.util.TypedValue; 67import android.view.Gravity; 68import android.view.KeyCharacterMap; 69import android.view.KeyEvent; 70import android.view.LayoutInflater; 71import android.view.Menu; 72import android.view.MenuItem; 73import android.view.MotionEvent; 74import android.view.View; 75import android.view.ViewConfiguration; 76import android.view.ViewGroup; 77import android.view.ViewParent; 78import android.view.Window; 79import android.view.WindowManager; 80import android.view.accessibility.AccessibilityEvent; 81import android.widget.FrameLayout; 82import android.widget.PopupWindow; 83import android.widget.TextView; 84 85import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 86import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 87import static android.view.Window.FEATURE_OPTIONS_PANEL; 88 89class AppCompatDelegateImplV7 extends AppCompatDelegateImplBase 90 implements MenuBuilder.Callback, LayoutInflaterFactory { 91 92 private DecorContentParent mDecorContentParent; 93 private ActionMenuPresenterCallback mActionMenuPresenterCallback; 94 private PanelMenuPresenterCallback mPanelMenuPresenterCallback; 95 96 ActionMode mActionMode; 97 ActionBarContextView mActionModeView; 98 PopupWindow mActionModePopup; 99 Runnable mShowActionModePopup; 100 ViewPropertyAnimatorCompat mFadeAnim = null; 101 102 // true if we have installed a window sub-decor layout. 103 private boolean mSubDecorInstalled; 104 private ViewGroup mSubDecor; 105 106 private TextView mTitleView; 107 private View mStatusGuard; 108 109 // Used to keep track of Progress Bar Window features 110 private boolean mFeatureProgress, mFeatureIndeterminateProgress; 111 112 // Used for emulating PanelFeatureState 113 private boolean mClosingActionMenu; 114 private PanelFeatureState[] mPanels; 115 private PanelFeatureState mPreparedPanel; 116 117 private boolean mLongPressBackDown; 118 119 private boolean mInvalidatePanelMenuPosted; 120 private int mInvalidatePanelMenuFeatures; 121 private final Runnable mInvalidatePanelMenuRunnable = new Runnable() { 122 @Override 123 public void run() { 124 if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_OPTIONS_PANEL) != 0) { 125 doInvalidatePanelMenu(FEATURE_OPTIONS_PANEL); 126 } 127 if ((mInvalidatePanelMenuFeatures & 1 << FEATURE_SUPPORT_ACTION_BAR) != 0) { 128 doInvalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR); 129 } 130 mInvalidatePanelMenuPosted = false; 131 mInvalidatePanelMenuFeatures = 0; 132 } 133 }; 134 135 private boolean mEnableDefaultActionBarUp; 136 137 private Rect mTempRect1; 138 private Rect mTempRect2; 139 140 private AppCompatViewInflater mAppCompatViewInflater; 141 142 AppCompatDelegateImplV7(Context context, Window window, AppCompatCallback callback) { 143 super(context, window, callback); 144 } 145 146 @Override 147 public void onCreate(Bundle savedInstanceState) { 148 if (mOriginalWindowCallback instanceof Activity) { 149 if (NavUtils.getParentActivityName((Activity) mOriginalWindowCallback) != null) { 150 // Peek at the Action Bar and update it if it already exists 151 ActionBar ab = peekSupportActionBar(); 152 if (ab == null) { 153 mEnableDefaultActionBarUp = true; 154 } else { 155 ab.setDefaultDisplayHomeAsUpEnabled(true); 156 } 157 } 158 } 159 } 160 161 @Override 162 public void onPostCreate(Bundle savedInstanceState) { 163 // Make sure that the sub decor is installed 164 ensureSubDecor(); 165 } 166 167 @Override 168 public void initWindowDecorActionBar() { 169 ensureSubDecor(); 170 171 if (!mHasActionBar || mActionBar != null) { 172 return; 173 } 174 175 if (mOriginalWindowCallback instanceof Activity) { 176 mActionBar = new WindowDecorActionBar((Activity) mOriginalWindowCallback, 177 mOverlayActionBar); 178 } else if (mOriginalWindowCallback instanceof Dialog) { 179 mActionBar = new WindowDecorActionBar((Dialog) mOriginalWindowCallback); 180 } 181 if (mActionBar != null) { 182 mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); 183 } 184 } 185 186 @Override 187 public void setSupportActionBar(Toolbar toolbar) { 188 if (!(mOriginalWindowCallback instanceof Activity)) { 189 // Only Activities support custom Action Bars 190 return; 191 } 192 193 final ActionBar ab = getSupportActionBar(); 194 if (ab instanceof WindowDecorActionBar) { 195 throw new IllegalStateException("This Activity already has an action bar supplied " + 196 "by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set " + 197 "windowActionBar to false in your theme to use a Toolbar instead."); 198 } 199 200 // If we reach here then we're setting a new action bar 201 // First clear out the MenuInflater to make sure that it is valid for the new Action Bar 202 mMenuInflater = null; 203 204 // If we have an action bar currently, destroy it 205 if (ab != null) { 206 ab.onDestroy(); 207 } 208 209 if (toolbar != null) { 210 final ToolbarActionBar tbab = new ToolbarActionBar(toolbar, 211 ((Activity) mContext).getTitle(), mAppCompatWindowCallback); 212 mActionBar = tbab; 213 mWindow.setCallback(tbab.getWrappedWindowCallback()); 214 } else { 215 mActionBar = null; 216 // Re-set the original window callback since we may have already set a Toolbar wrapper 217 mWindow.setCallback(mAppCompatWindowCallback); 218 } 219 220 invalidateOptionsMenu(); 221 } 222 223 @Override 224 public void onConfigurationChanged(Configuration newConfig) { 225 // If this is called before sub-decor is installed, ActionBar will not 226 // be properly initialized. 227 if (mHasActionBar && mSubDecorInstalled) { 228 // Note: The action bar will need to access 229 // view changes from superclass. 230 ActionBar ab = getSupportActionBar(); 231 if (ab != null) { 232 ab.onConfigurationChanged(newConfig); 233 } 234 } 235 } 236 237 @Override 238 public void onStop() { 239 ActionBar ab = getSupportActionBar(); 240 if (ab != null) { 241 ab.setShowHideAnimationEnabled(false); 242 } 243 } 244 245 @Override 246 public void onPostResume() { 247 ActionBar ab = getSupportActionBar(); 248 if (ab != null) { 249 ab.setShowHideAnimationEnabled(true); 250 } 251 } 252 253 @Override 254 public void setContentView(View v) { 255 ensureSubDecor(); 256 ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); 257 contentParent.removeAllViews(); 258 contentParent.addView(v); 259 mOriginalWindowCallback.onContentChanged(); 260 } 261 262 @Override 263 public void setContentView(int resId) { 264 ensureSubDecor(); 265 ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); 266 contentParent.removeAllViews(); 267 LayoutInflater.from(mContext).inflate(resId, contentParent); 268 mOriginalWindowCallback.onContentChanged(); 269 } 270 271 @Override 272 public void setContentView(View v, ViewGroup.LayoutParams lp) { 273 ensureSubDecor(); 274 ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); 275 contentParent.removeAllViews(); 276 contentParent.addView(v, lp); 277 mOriginalWindowCallback.onContentChanged(); 278 } 279 280 @Override 281 public void addContentView(View v, ViewGroup.LayoutParams lp) { 282 ensureSubDecor(); 283 ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content); 284 contentParent.addView(v, lp); 285 mOriginalWindowCallback.onContentChanged(); 286 } 287 288 @Override 289 public void onDestroy() { 290 super.onDestroy(); 291 292 if (mActionBar != null) { 293 mActionBar.onDestroy(); 294 mActionBar = null; 295 } 296 } 297 298 private void ensureSubDecor() { 299 if (!mSubDecorInstalled) { 300 mSubDecor = createSubDecor(); 301 302 // If a title was set before we installed the decor, propogate it now 303 CharSequence title = getTitle(); 304 if (!TextUtils.isEmpty(title)) { 305 onTitleChanged(title); 306 } 307 308 applyFixedSizeWindow(); 309 310 onSubDecorInstalled(mSubDecor); 311 312 mSubDecorInstalled = true; 313 314 // Invalidate if the panel menu hasn't been created before this. 315 // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu 316 // being called in the middle of onCreate or similar. 317 // A pending invalidation will typically be resolved before the posted message 318 // would run normally in order to satisfy instance state restoration. 319 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 320 if (!isDestroyed() && (st == null || st.menu == null)) { 321 invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR); 322 } 323 } 324 } 325 326 private ViewGroup createSubDecor() { 327 TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); 328 329 if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) { 330 a.recycle(); 331 throw new IllegalStateException( 332 "You need to use a Theme.AppCompat theme (or descendant) with this activity."); 333 } 334 335 if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) { 336 requestWindowFeature(Window.FEATURE_NO_TITLE); 337 } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) { 338 // Don't allow an action bar if there is no title. 339 requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR); 340 } 341 if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) { 342 requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY); 343 } 344 if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) { 345 requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY); 346 } 347 mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false); 348 a.recycle(); 349 350 final LayoutInflater inflater = LayoutInflater.from(mContext); 351 ViewGroup subDecor = null; 352 353 354 if (!mWindowNoTitle) { 355 if (mIsFloating) { 356 // If we're floating, inflate the dialog title decor 357 subDecor = (ViewGroup) inflater.inflate( 358 R.layout.abc_dialog_title_material, null); 359 360 // Floating windows can never have an action bar, reset the flags 361 mHasActionBar = mOverlayActionBar = false; 362 } else if (mHasActionBar) { 363 /** 364 * This needs some explanation. As we can not use the android:theme attribute 365 * pre-L, we emulate it by manually creating a LayoutInflater using a 366 * ContextThemeWrapper pointing to actionBarTheme. 367 */ 368 TypedValue outValue = new TypedValue(); 369 mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true); 370 371 Context themedContext; 372 if (outValue.resourceId != 0) { 373 themedContext = new ContextThemeWrapper(mContext, outValue.resourceId); 374 } else { 375 themedContext = mContext; 376 } 377 378 // Now inflate the view using the themed context and set it as the content view 379 subDecor = (ViewGroup) LayoutInflater.from(themedContext) 380 .inflate(R.layout.abc_screen_toolbar, null); 381 382 mDecorContentParent = (DecorContentParent) subDecor 383 .findViewById(R.id.decor_content_parent); 384 mDecorContentParent.setWindowCallback(getWindowCallback()); 385 386 /** 387 * Propagate features to DecorContentParent 388 */ 389 if (mOverlayActionBar) { 390 mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY); 391 } 392 if (mFeatureProgress) { 393 mDecorContentParent.initFeature(Window.FEATURE_PROGRESS); 394 } 395 if (mFeatureIndeterminateProgress) { 396 mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 397 } 398 } 399 } else { 400 if (mOverlayActionMode) { 401 subDecor = (ViewGroup) inflater.inflate( 402 R.layout.abc_screen_simple_overlay_action_mode, null); 403 } else { 404 subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null); 405 } 406 407 if (Build.VERSION.SDK_INT >= 21) { 408 // If we're running on L or above, we can rely on ViewCompat's 409 // setOnApplyWindowInsetsListener 410 ViewCompat.setOnApplyWindowInsetsListener(subDecor, 411 new OnApplyWindowInsetsListener() { 412 @Override 413 public WindowInsetsCompat onApplyWindowInsets(View v, 414 WindowInsetsCompat insets) { 415 final int top = insets.getSystemWindowInsetTop(); 416 final int newTop = updateStatusGuard(top); 417 418 if (top != newTop) { 419 insets = insets.replaceSystemWindowInsets( 420 insets.getSystemWindowInsetLeft(), 421 newTop, 422 insets.getSystemWindowInsetRight(), 423 insets.getSystemWindowInsetBottom()); 424 } 425 426 // Now apply the insets on our view 427 return ViewCompat.onApplyWindowInsets(v, insets); 428 } 429 }); 430 } else { 431 // Else, we need to use our own FitWindowsViewGroup handling 432 ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener( 433 new FitWindowsViewGroup.OnFitSystemWindowsListener() { 434 @Override 435 public void onFitSystemWindows(Rect insets) { 436 insets.top = updateStatusGuard(insets.top); 437 } 438 }); 439 } 440 } 441 442 if (subDecor == null) { 443 throw new IllegalArgumentException( 444 "AppCompat does not support the current theme features: { " 445 + "windowActionBar: " + mHasActionBar 446 + ", windowActionBarOverlay: "+ mOverlayActionBar 447 + ", android:windowIsFloating: " + mIsFloating 448 + ", windowActionModeOverlay: " + mOverlayActionMode 449 + ", windowNoTitle: " + mWindowNoTitle 450 + " }"); 451 } 452 453 if (mDecorContentParent == null) { 454 mTitleView = (TextView) subDecor.findViewById(R.id.title); 455 } 456 457 // Make the decor optionally fit system windows, like the window's decor 458 ViewUtils.makeOptionalFitsSystemWindows(subDecor); 459 460 final ViewGroup decorContent = (ViewGroup) mWindow.findViewById(android.R.id.content); 461 final ContentFrameLayout abcContent = (ContentFrameLayout) subDecor.findViewById( 462 R.id.action_bar_activity_content); 463 464 // There might be Views already added to the Window's content view so we need to 465 // migrate them to our content view 466 while (decorContent.getChildCount() > 0) { 467 final View child = decorContent.getChildAt(0); 468 decorContent.removeViewAt(0); 469 abcContent.addView(child); 470 } 471 472 // Now set the Window's content view with the decor 473 mWindow.setContentView(subDecor); 474 475 // Change our content FrameLayout to use the android.R.id.content id. 476 // Useful for fragments. 477 decorContent.setId(View.NO_ID); 478 abcContent.setId(android.R.id.content); 479 480 // The decorContent may have a foreground drawable set (windowContentOverlay). 481 // Remove this as we handle it ourselves 482 if (decorContent instanceof FrameLayout) { 483 ((FrameLayout) decorContent).setForeground(null); 484 } 485 486 abcContent.setAttachListener(new ContentFrameLayout.OnAttachListener() { 487 @Override 488 public void onAttachedFromWindow() {} 489 490 @Override 491 public void onDetachedFromWindow() { 492 dismissPopups(); 493 } 494 }); 495 496 return subDecor; 497 } 498 499 void onSubDecorInstalled(ViewGroup subDecor) {} 500 501 private void applyFixedSizeWindow() { 502 ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content); 503 504 // This is a bit weird. In the framework, the window sizing attributes control 505 // the decor view's size, meaning that any padding is inset for the min/max widths below. 506 // We don't control measurement at that level, so we need to workaround it by making sure 507 // that the decor view's padding is taken into account. 508 final View windowDecor = mWindow.getDecorView(); 509 cfl.setDecorPadding(windowDecor.getPaddingLeft(), 510 windowDecor.getPaddingTop(), windowDecor.getPaddingRight(), 511 windowDecor.getPaddingBottom()); 512 513 TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); 514 a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor()); 515 a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor()); 516 517 if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) { 518 a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor, 519 cfl.getFixedWidthMajor()); 520 } 521 if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) { 522 a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor, 523 cfl.getFixedWidthMinor()); 524 } 525 if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) { 526 a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor, 527 cfl.getFixedHeightMajor()); 528 } 529 if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) { 530 a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor, 531 cfl.getFixedHeightMinor()); 532 } 533 a.recycle(); 534 535 cfl.requestLayout(); 536 } 537 538 @Override 539 public boolean requestWindowFeature(int featureId) { 540 featureId = sanitizeWindowFeatureId(featureId); 541 542 if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) { 543 return false; // Ignore. No title dominates. 544 } 545 if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) { 546 // Remove the action bar feature if we have no title. No title dominates. 547 mHasActionBar = false; 548 } 549 550 switch (featureId) { 551 case FEATURE_SUPPORT_ACTION_BAR: 552 throwFeatureRequestIfSubDecorInstalled(); 553 mHasActionBar = true; 554 return true; 555 case FEATURE_SUPPORT_ACTION_BAR_OVERLAY: 556 throwFeatureRequestIfSubDecorInstalled(); 557 mOverlayActionBar = true; 558 return true; 559 case FEATURE_ACTION_MODE_OVERLAY: 560 throwFeatureRequestIfSubDecorInstalled(); 561 mOverlayActionMode = true; 562 return true; 563 case Window.FEATURE_PROGRESS: 564 throwFeatureRequestIfSubDecorInstalled(); 565 mFeatureProgress = true; 566 return true; 567 case Window.FEATURE_INDETERMINATE_PROGRESS: 568 throwFeatureRequestIfSubDecorInstalled(); 569 mFeatureIndeterminateProgress = true; 570 return true; 571 case Window.FEATURE_NO_TITLE: 572 throwFeatureRequestIfSubDecorInstalled(); 573 mWindowNoTitle = true; 574 return true; 575 } 576 577 return mWindow.requestFeature(featureId); 578 } 579 580 @Override 581 public boolean hasWindowFeature(int featureId) { 582 featureId = sanitizeWindowFeatureId(featureId); 583 switch (featureId) { 584 case FEATURE_SUPPORT_ACTION_BAR: 585 return mHasActionBar; 586 case FEATURE_SUPPORT_ACTION_BAR_OVERLAY: 587 return mOverlayActionBar; 588 case FEATURE_ACTION_MODE_OVERLAY: 589 return mOverlayActionMode; 590 case Window.FEATURE_PROGRESS: 591 return mFeatureProgress; 592 case Window.FEATURE_INDETERMINATE_PROGRESS: 593 return mFeatureIndeterminateProgress; 594 case Window.FEATURE_NO_TITLE: 595 return mWindowNoTitle; 596 } 597 return mWindow.hasFeature(featureId); 598 } 599 600 @Override 601 void onTitleChanged(CharSequence title) { 602 if (mDecorContentParent != null) { 603 mDecorContentParent.setWindowTitle(title); 604 } else if (peekSupportActionBar() != null) { 605 peekSupportActionBar().setWindowTitle(title); 606 } else if (mTitleView != null) { 607 mTitleView.setText(title); 608 } 609 } 610 611 @Override 612 void onPanelClosed(final int featureId, Menu menu) { 613 if (featureId == FEATURE_SUPPORT_ACTION_BAR) { 614 ActionBar ab = getSupportActionBar(); 615 if (ab != null) { 616 ab.dispatchMenuVisibilityChanged(false); 617 } 618 } else if (featureId == FEATURE_OPTIONS_PANEL) { 619 // Make sure that the options panel is closed. This is mainly used when we're using a 620 // ToolbarActionBar 621 PanelFeatureState st = getPanelState(featureId, true); 622 if (st.isOpen) { 623 closePanel(st, false); 624 } 625 } 626 } 627 628 @Override 629 boolean onMenuOpened(final int featureId, Menu menu) { 630 if (featureId == FEATURE_SUPPORT_ACTION_BAR) { 631 ActionBar ab = getSupportActionBar(); 632 if (ab != null) { 633 ab.dispatchMenuVisibilityChanged(true); 634 } 635 return true; 636 } 637 return false; 638 } 639 640 @Override 641 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 642 final Window.Callback cb = getWindowCallback(); 643 if (cb != null && !isDestroyed()) { 644 final PanelFeatureState panel = findMenuPanel(menu.getRootMenu()); 645 if (panel != null) { 646 return cb.onMenuItemSelected(panel.featureId, item); 647 } 648 } 649 return false; 650 } 651 652 @Override 653 public void onMenuModeChange(MenuBuilder menu) { 654 reopenMenu(menu, true); 655 } 656 657 @Override 658 public ActionMode startSupportActionMode(ActionMode.Callback callback) { 659 if (callback == null) { 660 throw new IllegalArgumentException("ActionMode callback can not be null."); 661 } 662 663 if (mActionMode != null) { 664 mActionMode.finish(); 665 } 666 667 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapperV7(callback); 668 669 ActionBar ab = getSupportActionBar(); 670 if (ab != null) { 671 mActionMode = ab.startActionMode(wrappedCallback); 672 if (mActionMode != null && mAppCompatCallback != null) { 673 mAppCompatCallback.onSupportActionModeStarted(mActionMode); 674 } 675 } 676 677 if (mActionMode == null) { 678 // If the action bar didn't provide an action mode, start the emulated window one 679 mActionMode = startSupportActionModeFromWindow(wrappedCallback); 680 } 681 682 return mActionMode; 683 } 684 685 @Override 686 public void invalidateOptionsMenu() { 687 final ActionBar ab = getSupportActionBar(); 688 if (ab != null && ab.invalidateOptionsMenu()) return; 689 690 invalidatePanelMenu(FEATURE_OPTIONS_PANEL); 691 } 692 693 @Override 694 ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback) { 695 endOnGoingFadeAnimation(); 696 if (mActionMode != null) { 697 mActionMode.finish(); 698 } 699 700 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapperV7(callback); 701 ActionMode mode = null; 702 if (mAppCompatCallback != null && !isDestroyed()) { 703 try { 704 mode = mAppCompatCallback.onWindowStartingSupportActionMode(wrappedCallback); 705 } catch (AbstractMethodError ame) { 706 // Older apps might not implement this callback method. 707 } 708 } 709 710 if (mode != null) { 711 mActionMode = mode; 712 } else { 713 if (mActionModeView == null) { 714 if (mIsFloating) { 715 // Use the action bar theme. 716 final TypedValue outValue = new TypedValue(); 717 final Resources.Theme baseTheme = mContext.getTheme(); 718 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); 719 720 final Context actionBarContext; 721 if (outValue.resourceId != 0) { 722 final Resources.Theme actionBarTheme = mContext.getResources().newTheme(); 723 actionBarTheme.setTo(baseTheme); 724 actionBarTheme.applyStyle(outValue.resourceId, true); 725 726 actionBarContext = new ContextThemeWrapper(mContext, 0); 727 actionBarContext.getTheme().setTo(actionBarTheme); 728 } else { 729 actionBarContext = mContext; 730 } 731 732 mActionModeView = new ActionBarContextView(actionBarContext); 733 mActionModePopup = new PopupWindow(actionBarContext, null, 734 R.attr.actionModePopupWindowStyle); 735 PopupWindowCompat.setWindowLayoutType(mActionModePopup, 736 WindowManager.LayoutParams.TYPE_APPLICATION); 737 mActionModePopup.setContentView(mActionModeView); 738 mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); 739 740 actionBarContext.getTheme().resolveAttribute( 741 R.attr.actionBarSize, outValue, true); 742 final int height = TypedValue.complexToDimensionPixelSize(outValue.data, 743 actionBarContext.getResources().getDisplayMetrics()); 744 mActionModeView.setContentHeight(height); 745 mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); 746 mShowActionModePopup = new Runnable() { 747 public void run() { 748 mActionModePopup.showAtLocation( 749 mActionModeView, 750 Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); 751 endOnGoingFadeAnimation(); 752 ViewCompat.setAlpha(mActionModeView, 0f); 753 mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f); 754 mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() { 755 @Override 756 public void onAnimationEnd(View view) { 757 ViewCompat.setAlpha(mActionModeView, 1f); 758 mFadeAnim.setListener(null); 759 mFadeAnim = null; 760 } 761 762 @Override 763 public void onAnimationStart(View view) { 764 mActionModeView.setVisibility(View.VISIBLE); 765 } 766 }); 767 } 768 }; 769 } else { 770 ViewStubCompat stub = (ViewStubCompat) mSubDecor 771 .findViewById(R.id.action_mode_bar_stub); 772 if (stub != null) { 773 // Set the layout inflater so that it is inflated with the action bar's context 774 stub.setLayoutInflater(LayoutInflater.from(getActionBarThemedContext())); 775 mActionModeView = (ActionBarContextView) stub.inflate(); 776 } 777 } 778 } 779 780 if (mActionModeView != null) { 781 endOnGoingFadeAnimation(); 782 mActionModeView.killMode(); 783 mode = new StandaloneActionMode(mActionModeView.getContext(), mActionModeView, 784 wrappedCallback, mActionModePopup == null); 785 if (callback.onCreateActionMode(mode, mode.getMenu())) { 786 mode.invalidate(); 787 mActionModeView.initForMode(mode); 788 mActionMode = mode; 789 ViewCompat.setAlpha(mActionModeView, 0f); 790 mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f); 791 mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() { 792 @Override 793 public void onAnimationEnd(View view) { 794 ViewCompat.setAlpha(mActionModeView, 1f); 795 mFadeAnim.setListener(null); 796 mFadeAnim = null; 797 } 798 799 @Override 800 public void onAnimationStart(View view) { 801 mActionModeView.setVisibility(View.VISIBLE); 802 mActionModeView.sendAccessibilityEvent( 803 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 804 if (mActionModeView.getParent() != null) { 805 ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); 806 } 807 } 808 }); 809 if (mActionModePopup != null) { 810 mWindow.getDecorView().post(mShowActionModePopup); 811 } 812 } else { 813 mActionMode = null; 814 } 815 } 816 } 817 if (mActionMode != null && mAppCompatCallback != null) { 818 mAppCompatCallback.onSupportActionModeStarted(mActionMode); 819 } 820 return mActionMode; 821 } 822 823 private void endOnGoingFadeAnimation() { 824 if (mFadeAnim != null) { 825 mFadeAnim.cancel(); 826 } 827 } 828 829 boolean onBackPressed() { 830 // Back cancels action modes first. 831 if (mActionMode != null) { 832 mActionMode.finish(); 833 return true; 834 } 835 836 // Next collapse any expanded action views. 837 ActionBar ab = getSupportActionBar(); 838 if (ab != null && ab.collapseActionView()) { 839 return true; 840 } 841 842 // Let the call through... 843 return false; 844 } 845 846 @Override 847 boolean onKeyShortcut(int keyCode, KeyEvent ev) { 848 // Let the Action Bar have a chance at handling the shortcut 849 ActionBar ab = getSupportActionBar(); 850 if (ab != null && ab.onKeyShortcut(keyCode, ev)) { 851 return true; 852 } 853 854 // If the panel is already prepared, then perform the shortcut using it. 855 boolean handled; 856 if (mPreparedPanel != null) { 857 handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev, 858 Menu.FLAG_PERFORM_NO_CLOSE); 859 if (handled) { 860 if (mPreparedPanel != null) { 861 mPreparedPanel.isHandled = true; 862 } 863 return true; 864 } 865 } 866 867 // If the panel is not prepared, then we may be trying to handle a shortcut key 868 // combination such as Control+C. Temporarily prepare the panel then mark it 869 // unprepared again when finished to ensure that the panel will again be prepared 870 // the next time it is shown for real. 871 if (mPreparedPanel == null) { 872 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 873 preparePanel(st, ev); 874 handled = performPanelShortcut(st, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE); 875 st.isPrepared = false; 876 if (handled) { 877 return true; 878 } 879 } 880 return false; 881 } 882 883 @Override 884 boolean dispatchKeyEvent(KeyEvent event) { 885 if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) { 886 // If this is a MENU event, let the Activity have a go. 887 if (mOriginalWindowCallback.dispatchKeyEvent(event)) { 888 return true; 889 } 890 } 891 892 final int keyCode = event.getKeyCode(); 893 final int action = event.getAction(); 894 final boolean isDown = action == KeyEvent.ACTION_DOWN; 895 896 return isDown ? onKeyDown(keyCode, event) : onKeyUp(keyCode, event); 897 } 898 899 boolean onKeyUp(int keyCode, KeyEvent event) { 900 switch (keyCode) { 901 case KeyEvent.KEYCODE_MENU: 902 onKeyUpPanel(Window.FEATURE_OPTIONS_PANEL, event); 903 return true; 904 case KeyEvent.KEYCODE_BACK: 905 final boolean wasLongPressBackDown = mLongPressBackDown; 906 mLongPressBackDown = false; 907 908 PanelFeatureState st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 909 if (st != null && st.isOpen) { 910 if (!wasLongPressBackDown) { 911 // Certain devices allow opening the options menu via a long press of the 912 // back button. We should only close the open options menu if it wasn't 913 // opened via a long press gesture. 914 closePanel(st, true); 915 } 916 return true; 917 } 918 if (onBackPressed()) { 919 return true; 920 } 921 break; 922 } 923 return false; 924 } 925 926 boolean onKeyDown(int keyCode, KeyEvent event) { 927 switch (keyCode) { 928 case KeyEvent.KEYCODE_MENU: 929 onKeyDownPanel(Window.FEATURE_OPTIONS_PANEL, event); 930 // We need to return true here and not let it bubble up to the Window. 931 // For empty menus, PhoneWindow's KEYCODE_BACK handling will steals all events, 932 // not allowing the Activity to call onBackPressed(). 933 return true; 934 case KeyEvent.KEYCODE_BACK: 935 // Certain devices allow opening the options menu via a long press of the back 936 // button. We keep a record of whether the last event is from a long press. 937 mLongPressBackDown = (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0; 938 break; 939 } 940 941 // On API v7-10 we need to manually call onKeyShortcut() as this is not called 942 // from the Activity 943 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { 944 // We do not return true here otherwise dispatchKeyEvent will not reach the Activity 945 // (which results in the back button not working) 946 onKeyShortcut(keyCode, event); 947 } 948 return false; 949 } 950 951 @Override 952 public View createView(View parent, final String name, @NonNull Context context, 953 @NonNull AttributeSet attrs) { 954 final boolean isPre21 = Build.VERSION.SDK_INT < 21; 955 956 if (mAppCompatViewInflater == null) { 957 mAppCompatViewInflater = new AppCompatViewInflater(); 958 } 959 960 // We only want the View to inherit its context if we're running pre-v21 961 final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent); 962 963 return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, 964 isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */ 965 true /* Read read app:theme as a fallback at all times for legacy reasons */ 966 ); 967 } 968 969 private boolean shouldInheritContext(ViewParent parent) { 970 if (parent == null) { 971 // The initial parent is null so just return false 972 return false; 973 } 974 final View windowDecor = mWindow.getDecorView(); 975 while (true) { 976 if (parent == null) { 977 // Bingo. We've hit a view which has a null parent before being terminated from 978 // the loop. This is (most probably) because it's the root view in an inflation 979 // call, therefore we should inherit. This works as the inflated layout is only 980 // added to the hierarchy at the end of the inflate() call. 981 return true; 982 } else if (parent == windowDecor || !(parent instanceof View) 983 || ViewCompat.isAttachedToWindow((View) parent)) { 984 // We have either hit the window's decor view, a parent which isn't a View 985 // (i.e. ViewRootImpl), or an attached view, so we know that the original parent 986 // is currently added to the view hierarchy. This means that it has not be 987 // inflated in the current inflate() call and we should not inherit the context. 988 return false; 989 } 990 parent = parent.getParent(); 991 } 992 } 993 994 @Override 995 public void installViewFactory() { 996 LayoutInflater layoutInflater = LayoutInflater.from(mContext); 997 if (layoutInflater.getFactory() == null) { 998 LayoutInflaterCompat.setFactory(layoutInflater, this); 999 } else { 1000 if (!(LayoutInflaterCompat.getFactory(layoutInflater) 1001 instanceof AppCompatDelegateImplV7)) { 1002 Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" 1003 + " so we can not install AppCompat's"); 1004 } 1005 } 1006 } 1007 1008 /** 1009 * From {@link android.support.v4.view.LayoutInflaterFactory} 1010 */ 1011 @Override 1012 public final View onCreateView(View parent, String name, 1013 Context context, AttributeSet attrs) { 1014 // First let the Activity's Factory try and inflate the view 1015 final View view = callActivityOnCreateView(parent, name, context, attrs); 1016 if (view != null) { 1017 return view; 1018 } 1019 1020 // If the Factory didn't handle it, let our createView() method try 1021 return createView(parent, name, context, attrs); 1022 } 1023 1024 View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) { 1025 // Let the Activity's LayoutInflater.Factory try and handle it 1026 if (mOriginalWindowCallback instanceof LayoutInflater.Factory) { 1027 final View result = ((LayoutInflater.Factory) mOriginalWindowCallback) 1028 .onCreateView(name, context, attrs); 1029 if (result != null) { 1030 return result; 1031 } 1032 } 1033 return null; 1034 } 1035 1036 private void openPanel(final PanelFeatureState st, KeyEvent event) { 1037 // Already open, return 1038 if (st.isOpen || isDestroyed()) { 1039 return; 1040 } 1041 1042 // Don't open an options panel for honeycomb apps on xlarge devices. 1043 // (The app should be using an action bar for menu items.) 1044 if (st.featureId == FEATURE_OPTIONS_PANEL) { 1045 Context context = mContext; 1046 Configuration config = context.getResources().getConfiguration(); 1047 boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == 1048 Configuration.SCREENLAYOUT_SIZE_XLARGE; 1049 boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >= 1050 android.os.Build.VERSION_CODES.HONEYCOMB; 1051 1052 if (isXLarge && isHoneycombApp) { 1053 return; 1054 } 1055 } 1056 1057 Window.Callback cb = getWindowCallback(); 1058 if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) { 1059 // Callback doesn't want the menu to open, reset any state 1060 closePanel(st, true); 1061 return; 1062 } 1063 1064 final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 1065 if (wm == null) { 1066 return; 1067 } 1068 1069 // Prepare panel (should have been done before, but just in case) 1070 if (!preparePanel(st, event)) { 1071 return; 1072 } 1073 1074 int width = WRAP_CONTENT; 1075 if (st.decorView == null || st.refreshDecorView) { 1076 if (st.decorView == null) { 1077 // Initialize the panel decor, this will populate st.decorView 1078 if (!initializePanelDecor(st) || (st.decorView == null)) 1079 return; 1080 } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) { 1081 // Decor needs refreshing, so remove its views 1082 st.decorView.removeAllViews(); 1083 } 1084 1085 // This will populate st.shownPanelView 1086 if (!initializePanelContent(st) || !st.hasPanelItems()) { 1087 return; 1088 } 1089 1090 ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams(); 1091 if (lp == null) { 1092 lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); 1093 } 1094 1095 int backgroundResId = st.background; 1096 st.decorView.setBackgroundResource(backgroundResId); 1097 1098 ViewParent shownPanelParent = st.shownPanelView.getParent(); 1099 if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) { 1100 ((ViewGroup) shownPanelParent).removeView(st.shownPanelView); 1101 } 1102 st.decorView.addView(st.shownPanelView, lp); 1103 1104 /* 1105 * Give focus to the view, if it or one of its children does not 1106 * already have it. 1107 */ 1108 if (!st.shownPanelView.hasFocus()) { 1109 st.shownPanelView.requestFocus(); 1110 } 1111 } else if (st.createdPanelView != null) { 1112 // If we already had a panel view, carry width=MATCH_PARENT through 1113 // as we did above when it was created. 1114 ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams(); 1115 if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { 1116 width = MATCH_PARENT; 1117 } 1118 } 1119 1120 st.isHandled = false; 1121 1122 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 1123 width, WRAP_CONTENT, 1124 st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, 1125 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 1126 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 1127 PixelFormat.TRANSLUCENT); 1128 1129 lp.gravity = st.gravity; 1130 lp.windowAnimations = st.windowAnimations; 1131 1132 wm.addView(st.decorView, lp); 1133 st.isOpen = true; 1134 } 1135 1136 private boolean initializePanelDecor(PanelFeatureState st) { 1137 st.setStyle(getActionBarThemedContext()); 1138 st.decorView = new ListMenuDecorView(st.listPresenterContext); 1139 st.gravity = Gravity.CENTER | Gravity.BOTTOM; 1140 return true; 1141 } 1142 1143 private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) { 1144 if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() && 1145 (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext)) || 1146 mDecorContentParent.isOverflowMenuShowPending())) { 1147 1148 final Window.Callback cb = getWindowCallback(); 1149 1150 if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) { 1151 if (cb != null && !isDestroyed()) { 1152 // If we have a menu invalidation pending, do it now. 1153 if (mInvalidatePanelMenuPosted && 1154 (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) { 1155 mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable); 1156 mInvalidatePanelMenuRunnable.run(); 1157 } 1158 1159 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1160 1161 // If we don't have a menu or we're waiting for a full content refresh, 1162 // forget it. This is a lingering event that no longer matters. 1163 if (st.menu != null && !st.refreshMenuContent && 1164 cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { 1165 cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, st.menu); 1166 mDecorContentParent.showOverflowMenu(); 1167 } 1168 } 1169 } else { 1170 mDecorContentParent.hideOverflowMenu(); 1171 if (!isDestroyed()) { 1172 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1173 cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, st.menu); 1174 } 1175 } 1176 return; 1177 } 1178 1179 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1180 1181 st.refreshDecorView = true; 1182 closePanel(st, false); 1183 1184 openPanel(st, null); 1185 } 1186 1187 private boolean initializePanelMenu(final PanelFeatureState st) { 1188 Context context = mContext; 1189 1190 // If we have an action bar, initialize the menu with the right theme. 1191 if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR) && 1192 mDecorContentParent != null) { 1193 final TypedValue outValue = new TypedValue(); 1194 final Resources.Theme baseTheme = context.getTheme(); 1195 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); 1196 1197 Resources.Theme widgetTheme = null; 1198 if (outValue.resourceId != 0) { 1199 widgetTheme = context.getResources().newTheme(); 1200 widgetTheme.setTo(baseTheme); 1201 widgetTheme.applyStyle(outValue.resourceId, true); 1202 widgetTheme.resolveAttribute( 1203 R.attr.actionBarWidgetTheme, outValue, true); 1204 } else { 1205 baseTheme.resolveAttribute( 1206 R.attr.actionBarWidgetTheme, outValue, true); 1207 } 1208 1209 if (outValue.resourceId != 0) { 1210 if (widgetTheme == null) { 1211 widgetTheme = context.getResources().newTheme(); 1212 widgetTheme.setTo(baseTheme); 1213 } 1214 widgetTheme.applyStyle(outValue.resourceId, true); 1215 } 1216 1217 if (widgetTheme != null) { 1218 context = new ContextThemeWrapper(context, 0); 1219 context.getTheme().setTo(widgetTheme); 1220 } 1221 } 1222 1223 final MenuBuilder menu = new MenuBuilder(context); 1224 menu.setCallback(this); 1225 st.setMenu(menu); 1226 1227 return true; 1228 } 1229 1230 private boolean initializePanelContent(PanelFeatureState st) { 1231 if (st.createdPanelView != null) { 1232 st.shownPanelView = st.createdPanelView; 1233 return true; 1234 } 1235 1236 if (st.menu == null) { 1237 return false; 1238 } 1239 1240 if (mPanelMenuPresenterCallback == null) { 1241 mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); 1242 } 1243 1244 MenuView menuView = st.getListMenuView(mPanelMenuPresenterCallback); 1245 1246 st.shownPanelView = (View) menuView; 1247 1248 return st.shownPanelView != null; 1249 } 1250 1251 private boolean preparePanel(PanelFeatureState st, KeyEvent event) { 1252 if (isDestroyed()) { 1253 return false; 1254 } 1255 1256 // Already prepared (isPrepared will be reset to false later) 1257 if (st.isPrepared) { 1258 return true; 1259 } 1260 1261 if ((mPreparedPanel != null) && (mPreparedPanel != st)) { 1262 // Another Panel is prepared and possibly open, so close it 1263 closePanel(mPreparedPanel, false); 1264 } 1265 1266 final Window.Callback cb = getWindowCallback(); 1267 1268 if (cb != null) { 1269 st.createdPanelView = cb.onCreatePanelView(st.featureId); 1270 } 1271 1272 final boolean isActionBarMenu = 1273 (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR); 1274 1275 if (isActionBarMenu && mDecorContentParent != null) { 1276 // Enforce ordering guarantees around events so that the action bar never 1277 // dispatches menu-related events before the panel is prepared. 1278 mDecorContentParent.setMenuPrepared(); 1279 } 1280 1281 if (st.createdPanelView == null && 1282 (!isActionBarMenu || !(peekSupportActionBar() instanceof ToolbarActionBar))) { 1283 // Since ToolbarActionBar handles the list options menu itself, we only want to 1284 // init this menu panel if we're not using a TAB. 1285 if (st.menu == null || st.refreshMenuContent) { 1286 if (st.menu == null) { 1287 if (!initializePanelMenu(st) || (st.menu == null)) { 1288 return false; 1289 } 1290 } 1291 1292 if (isActionBarMenu && mDecorContentParent != null) { 1293 if (mActionMenuPresenterCallback == null) { 1294 mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); 1295 } 1296 mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback); 1297 } 1298 1299 // Creating the panel menu will involve a lot of manipulation; 1300 // don't dispatch change events to presenters until we're done. 1301 st.menu.stopDispatchingItemsChanged(); 1302 if (!cb.onCreatePanelMenu(st.featureId, st.menu)) { 1303 // Ditch the menu created above 1304 st.setMenu(null); 1305 1306 if (isActionBarMenu && mDecorContentParent != null) { 1307 // Don't show it in the action bar either 1308 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 1309 } 1310 1311 return false; 1312 } 1313 1314 st.refreshMenuContent = false; 1315 } 1316 1317 // Preparing the panel menu can involve a lot of manipulation; 1318 // don't dispatch change events to presenters until we're done. 1319 st.menu.stopDispatchingItemsChanged(); 1320 1321 // Restore action view state before we prepare. This gives apps 1322 // an opportunity to override frozen/restored state in onPrepare. 1323 if (st.frozenActionViewState != null) { 1324 st.menu.restoreActionViewStates(st.frozenActionViewState); 1325 st.frozenActionViewState = null; 1326 } 1327 1328 // Callback and return if the callback does not want to show the menu 1329 if (!cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { 1330 if (isActionBarMenu && mDecorContentParent != null) { 1331 // The app didn't want to show the menu for now but it still exists. 1332 // Clear it out of the action bar. 1333 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 1334 } 1335 st.menu.startDispatchingItemsChanged(); 1336 return false; 1337 } 1338 1339 // Set the proper keymap 1340 KeyCharacterMap kmap = KeyCharacterMap.load( 1341 event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD); 1342 st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC; 1343 st.menu.setQwertyMode(st.qwertyMode); 1344 st.menu.startDispatchingItemsChanged(); 1345 } 1346 1347 // Set other state 1348 st.isPrepared = true; 1349 st.isHandled = false; 1350 mPreparedPanel = st; 1351 1352 return true; 1353 } 1354 1355 private void checkCloseActionMenu(MenuBuilder menu) { 1356 if (mClosingActionMenu) { 1357 return; 1358 } 1359 1360 mClosingActionMenu = true; 1361 mDecorContentParent.dismissPopups(); 1362 Window.Callback cb = getWindowCallback(); 1363 if (cb != null && !isDestroyed()) { 1364 cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, menu); 1365 } 1366 mClosingActionMenu = false; 1367 } 1368 1369 private void closePanel(int featureId) { 1370 closePanel(getPanelState(featureId, true), true); 1371 } 1372 1373 private void closePanel(PanelFeatureState st, boolean doCallback) { 1374 if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL && 1375 mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) { 1376 checkCloseActionMenu(st.menu); 1377 return; 1378 } 1379 1380 final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 1381 if (wm != null && st.isOpen && st.decorView != null) { 1382 wm.removeView(st.decorView); 1383 1384 if (doCallback) { 1385 callOnPanelClosed(st.featureId, st, null); 1386 } 1387 } 1388 1389 st.isPrepared = false; 1390 st.isHandled = false; 1391 st.isOpen = false; 1392 1393 // This view is no longer shown, so null it out 1394 st.shownPanelView = null; 1395 1396 // Next time the menu opens, it should not be in expanded mode, so 1397 // force a refresh of the decor 1398 st.refreshDecorView = true; 1399 1400 if (mPreparedPanel == st) { 1401 mPreparedPanel = null; 1402 } 1403 } 1404 1405 private boolean onKeyDownPanel(int featureId, KeyEvent event) { 1406 if (event.getRepeatCount() == 0) { 1407 PanelFeatureState st = getPanelState(featureId, true); 1408 if (!st.isOpen) { 1409 return preparePanel(st, event); 1410 } 1411 } 1412 1413 return false; 1414 } 1415 1416 private boolean onKeyUpPanel(int featureId, KeyEvent event) { 1417 if (mActionMode != null) { 1418 return false; 1419 } 1420 1421 boolean handled = false; 1422 final PanelFeatureState st = getPanelState(featureId, true); 1423 if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null && 1424 mDecorContentParent.canShowOverflowMenu() && 1425 !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext))) { 1426 if (!mDecorContentParent.isOverflowMenuShowing()) { 1427 if (!isDestroyed() && preparePanel(st, event)) { 1428 handled = mDecorContentParent.showOverflowMenu(); 1429 } 1430 } else { 1431 handled = mDecorContentParent.hideOverflowMenu(); 1432 } 1433 } else { 1434 if (st.isOpen || st.isHandled) { 1435 // Play the sound effect if the user closed an open menu (and not if 1436 // they just released a menu shortcut) 1437 handled = st.isOpen; 1438 // Close menu 1439 closePanel(st, true); 1440 } else if (st.isPrepared) { 1441 boolean show = true; 1442 if (st.refreshMenuContent) { 1443 // Something may have invalidated the menu since we prepared it. 1444 // Re-prepare it to refresh. 1445 st.isPrepared = false; 1446 show = preparePanel(st, event); 1447 } 1448 1449 if (show) { 1450 // Show menu 1451 openPanel(st, event); 1452 handled = true; 1453 } 1454 } 1455 } 1456 1457 if (handled) { 1458 AudioManager audioManager = (AudioManager) mContext.getSystemService( 1459 Context.AUDIO_SERVICE); 1460 if (audioManager != null) { 1461 audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); 1462 } else { 1463 Log.w(TAG, "Couldn't get audio manager"); 1464 } 1465 } 1466 return handled; 1467 } 1468 1469 private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) { 1470 // Try to get a menu 1471 if (menu == null) { 1472 // Need a panel to grab the menu, so try to get that 1473 if (panel == null) { 1474 if ((featureId >= 0) && (featureId < mPanels.length)) { 1475 panel = mPanels[featureId]; 1476 } 1477 } 1478 1479 if (panel != null) { 1480 // menu still may be null, which is okay--we tried our best 1481 menu = panel.menu; 1482 } 1483 } 1484 1485 // If the panel is not open, do not callback 1486 if ((panel != null) && (!panel.isOpen)) 1487 return; 1488 1489 if (!isDestroyed()) { 1490 // We need to be careful which callback we dispatch the call to. We can not dispatch 1491 // this to the Window's callback since that will call back into this method and cause a 1492 // crash. Instead we need to dispatch down to the original Activity/Dialog/etc. 1493 mOriginalWindowCallback.onPanelClosed(featureId, menu); 1494 } 1495 } 1496 1497 private PanelFeatureState findMenuPanel(Menu menu) { 1498 final PanelFeatureState[] panels = mPanels; 1499 final int N = panels != null ? panels.length : 0; 1500 for (int i = 0; i < N; i++) { 1501 final PanelFeatureState panel = panels[i]; 1502 if (panel != null && panel.menu == menu) { 1503 return panel; 1504 } 1505 } 1506 return null; 1507 } 1508 1509 private PanelFeatureState getPanelState(int featureId, boolean required) { 1510 PanelFeatureState[] ar; 1511 if ((ar = mPanels) == null || ar.length <= featureId) { 1512 PanelFeatureState[] nar = new PanelFeatureState[featureId + 1]; 1513 if (ar != null) { 1514 System.arraycopy(ar, 0, nar, 0, ar.length); 1515 } 1516 mPanels = ar = nar; 1517 } 1518 1519 PanelFeatureState st = ar[featureId]; 1520 if (st == null) { 1521 ar[featureId] = st = new PanelFeatureState(featureId); 1522 } 1523 return st; 1524 } 1525 1526 private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, 1527 int flags) { 1528 if (event.isSystem()) { 1529 return false; 1530 } 1531 1532 boolean handled = false; 1533 1534 // Only try to perform menu shortcuts if preparePanel returned true (possible false 1535 // return value from application not wanting to show the menu). 1536 if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) { 1537 // The menu is prepared now, perform the shortcut on it 1538 handled = st.menu.performShortcut(keyCode, event, flags); 1539 } 1540 1541 if (handled) { 1542 // Only close down the menu if we don't have an action bar keeping it open. 1543 if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) { 1544 closePanel(st, true); 1545 } 1546 } 1547 1548 return handled; 1549 } 1550 1551 private void invalidatePanelMenu(int featureId) { 1552 mInvalidatePanelMenuFeatures |= 1 << featureId; 1553 1554 if (!mInvalidatePanelMenuPosted) { 1555 ViewCompat.postOnAnimation(mWindow.getDecorView(), mInvalidatePanelMenuRunnable); 1556 mInvalidatePanelMenuPosted = true; 1557 } 1558 } 1559 1560 private void doInvalidatePanelMenu(int featureId) { 1561 PanelFeatureState st = getPanelState(featureId, true); 1562 Bundle savedActionViewStates = null; 1563 if (st.menu != null) { 1564 savedActionViewStates = new Bundle(); 1565 st.menu.saveActionViewStates(savedActionViewStates); 1566 if (savedActionViewStates.size() > 0) { 1567 st.frozenActionViewState = savedActionViewStates; 1568 } 1569 // This will be started again when the panel is prepared. 1570 st.menu.stopDispatchingItemsChanged(); 1571 st.menu.clear(); 1572 } 1573 st.refreshMenuContent = true; 1574 st.refreshDecorView = true; 1575 1576 // Prepare the options panel if we have an action bar 1577 if ((featureId == FEATURE_SUPPORT_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL) 1578 && mDecorContentParent != null) { 1579 st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 1580 if (st != null) { 1581 st.isPrepared = false; 1582 preparePanel(st, null); 1583 } 1584 } 1585 } 1586 1587 /** 1588 * Updates the status bar guard 1589 * 1590 * @param insetTop the current top system window inset 1591 * @return the new top system window inset 1592 */ 1593 private int updateStatusGuard(int insetTop) { 1594 boolean showStatusGuard = false; 1595 // Show the status guard when the non-overlay contextual action bar is showing 1596 if (mActionModeView != null) { 1597 if (mActionModeView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 1598 ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) 1599 mActionModeView.getLayoutParams(); 1600 boolean mlpChanged = false; 1601 1602 if (mActionModeView.isShown()) { 1603 if (mTempRect1 == null) { 1604 mTempRect1 = new Rect(); 1605 mTempRect2 = new Rect(); 1606 } 1607 final Rect insets = mTempRect1; 1608 final Rect localInsets = mTempRect2; 1609 insets.set(0, insetTop, 0, 0); 1610 1611 ViewUtils.computeFitSystemWindows(mSubDecor, insets, localInsets); 1612 final int newMargin = localInsets.top == 0 ? insetTop : 0; 1613 if (mlp.topMargin != newMargin) { 1614 mlpChanged = true; 1615 mlp.topMargin = insetTop; 1616 1617 if (mStatusGuard == null) { 1618 mStatusGuard = new View(mContext); 1619 mStatusGuard.setBackgroundColor(mContext.getResources() 1620 .getColor(R.color.abc_input_method_navigation_guard)); 1621 mSubDecor.addView(mStatusGuard, -1, 1622 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1623 insetTop)); 1624 } else { 1625 ViewGroup.LayoutParams lp = mStatusGuard.getLayoutParams(); 1626 if (lp.height != insetTop) { 1627 lp.height = insetTop; 1628 mStatusGuard.setLayoutParams(lp); 1629 } 1630 } 1631 } 1632 1633 // The action mode's theme may differ from the app, so 1634 // always show the status guard above it. 1635 showStatusGuard = mStatusGuard != null; 1636 1637 // We only need to consume the insets if the action 1638 // mode is overlaid on the app content (e.g. it's 1639 // sitting in a FrameLayout, see 1640 // screen_simple_overlay_action_mode.xml). 1641 if (!mOverlayActionMode && showStatusGuard) { 1642 insetTop = 0; 1643 } 1644 } else { 1645 // reset top margin 1646 if (mlp.topMargin != 0) { 1647 mlpChanged = true; 1648 mlp.topMargin = 0; 1649 } 1650 } 1651 if (mlpChanged) { 1652 mActionModeView.setLayoutParams(mlp); 1653 } 1654 } 1655 } 1656 if (mStatusGuard != null) { 1657 mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE); 1658 } 1659 1660 return insetTop; 1661 } 1662 1663 private void throwFeatureRequestIfSubDecorInstalled() { 1664 if (mSubDecorInstalled) { 1665 throw new AndroidRuntimeException( 1666 "Window feature must be requested before adding content"); 1667 } 1668 } 1669 1670 private int sanitizeWindowFeatureId(int featureId) { 1671 if (featureId == WindowCompat.FEATURE_ACTION_BAR) { 1672 Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR" 1673 + " id when requesting this feature."); 1674 return FEATURE_SUPPORT_ACTION_BAR; 1675 } else if (featureId == WindowCompat.FEATURE_ACTION_BAR_OVERLAY) { 1676 Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR_OVERLAY" 1677 + " id when requesting this feature."); 1678 return FEATURE_SUPPORT_ACTION_BAR_OVERLAY; 1679 } 1680 // Else we'll just return the original id 1681 return featureId; 1682 } 1683 1684 ViewGroup getSubDecor() { 1685 return mSubDecor; 1686 } 1687 1688 private void dismissPopups() { 1689 if (mDecorContentParent != null) { 1690 mDecorContentParent.dismissPopups(); 1691 } 1692 1693 if (mActionModePopup != null) { 1694 mWindow.getDecorView().removeCallbacks(mShowActionModePopup); 1695 if (mActionModePopup.isShowing()) { 1696 try { 1697 mActionModePopup.dismiss(); 1698 } catch (IllegalArgumentException e) { 1699 // Pre-v18, there are times when the Window will remove the popup before us. 1700 // In these cases we need to swallow the resulting exception. 1701 } 1702 } 1703 mActionModePopup = null; 1704 } 1705 endOnGoingFadeAnimation(); 1706 1707 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 1708 if (st != null && st.menu != null) { 1709 st.menu.close(); 1710 } 1711 } 1712 1713 /** 1714 * Clears out internal reference when the action mode is destroyed. 1715 */ 1716 class ActionModeCallbackWrapperV7 implements ActionMode.Callback { 1717 private ActionMode.Callback mWrapped; 1718 1719 public ActionModeCallbackWrapperV7(ActionMode.Callback wrapped) { 1720 mWrapped = wrapped; 1721 } 1722 1723 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 1724 return mWrapped.onCreateActionMode(mode, menu); 1725 } 1726 1727 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 1728 return mWrapped.onPrepareActionMode(mode, menu); 1729 } 1730 1731 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 1732 return mWrapped.onActionItemClicked(mode, item); 1733 } 1734 1735 public void onDestroyActionMode(ActionMode mode) { 1736 mWrapped.onDestroyActionMode(mode); 1737 if (mActionModePopup != null) { 1738 mWindow.getDecorView().removeCallbacks(mShowActionModePopup); 1739 } 1740 1741 if (mActionModeView != null) { 1742 endOnGoingFadeAnimation(); 1743 mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0f); 1744 mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() { 1745 @Override 1746 public void onAnimationEnd(View view) { 1747 mActionModeView.setVisibility(View.GONE); 1748 if (mActionModePopup != null) { 1749 mActionModePopup.dismiss(); 1750 } else if (mActionModeView.getParent() instanceof View) { 1751 ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); 1752 } 1753 mActionModeView.removeAllViews(); 1754 mFadeAnim.setListener(null); 1755 mFadeAnim = null; 1756 } 1757 }); 1758 } 1759 if (mAppCompatCallback != null) { 1760 mAppCompatCallback.onSupportActionModeFinished(mActionMode); 1761 } 1762 mActionMode = null; 1763 } 1764 } 1765 1766 private final class PanelMenuPresenterCallback implements MenuPresenter.Callback { 1767 @Override 1768 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1769 final Menu parentMenu = menu.getRootMenu(); 1770 final boolean isSubMenu = parentMenu != menu; 1771 final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu); 1772 if (panel != null) { 1773 if (isSubMenu) { 1774 callOnPanelClosed(panel.featureId, panel, parentMenu); 1775 closePanel(panel, true); 1776 } else { 1777 // Close the panel and only do the callback if the menu is being 1778 // closed completely, not if opening a sub menu 1779 closePanel(panel, allMenusAreClosing); 1780 } 1781 } 1782 } 1783 1784 @Override 1785 public boolean onOpenSubMenu(MenuBuilder subMenu) { 1786 if (subMenu == null && mHasActionBar) { 1787 Window.Callback cb = getWindowCallback(); 1788 if (cb != null && !isDestroyed()) { 1789 cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu); 1790 } 1791 } 1792 return true; 1793 } 1794 } 1795 1796 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { 1797 @Override 1798 public boolean onOpenSubMenu(MenuBuilder subMenu) { 1799 Window.Callback cb = getWindowCallback(); 1800 if (cb != null) { 1801 cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu); 1802 } 1803 return true; 1804 } 1805 1806 @Override 1807 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1808 checkCloseActionMenu(menu); 1809 } 1810 } 1811 1812 private static final class PanelFeatureState { 1813 1814 /** Feature ID for this panel. */ 1815 int featureId; 1816 1817 int background; 1818 1819 int gravity; 1820 1821 int x; 1822 1823 int y; 1824 1825 int windowAnimations; 1826 1827 /** Dynamic state of the panel. */ 1828 ViewGroup decorView; 1829 1830 /** The panel that we are actually showing. */ 1831 View shownPanelView; 1832 1833 /** The panel that was returned by onCreatePanelView(). */ 1834 View createdPanelView; 1835 1836 /** Use {@link #setMenu} to set this. */ 1837 MenuBuilder menu; 1838 1839 ListMenuPresenter listMenuPresenter; 1840 1841 Context listPresenterContext; 1842 1843 /** 1844 * Whether the panel has been prepared (see 1845 * {@link #preparePanel}). 1846 */ 1847 boolean isPrepared; 1848 1849 /** 1850 * Whether an item's action has been performed. This happens in obvious 1851 * scenarios (user clicks on menu item), but can also happen with 1852 * chording menu+(shortcut key). 1853 */ 1854 boolean isHandled; 1855 1856 boolean isOpen; 1857 1858 public boolean qwertyMode; 1859 1860 boolean refreshDecorView; 1861 1862 boolean refreshMenuContent; 1863 1864 boolean wasLastOpen; 1865 1866 /** 1867 * Contains the state of the menu when told to freeze. 1868 */ 1869 Bundle frozenMenuState; 1870 1871 /** 1872 * Contains the state of associated action views when told to freeze. 1873 * These are saved across invalidations. 1874 */ 1875 Bundle frozenActionViewState; 1876 1877 PanelFeatureState(int featureId) { 1878 this.featureId = featureId; 1879 1880 refreshDecorView = false; 1881 } 1882 1883 public boolean hasPanelItems() { 1884 if (shownPanelView == null) return false; 1885 if (createdPanelView != null) return true; 1886 1887 return listMenuPresenter.getAdapter().getCount() > 0; 1888 } 1889 1890 /** 1891 * Unregister and free attached MenuPresenters. They will be recreated as needed. 1892 */ 1893 public void clearMenuPresenters() { 1894 if (menu != null) { 1895 menu.removeMenuPresenter(listMenuPresenter); 1896 } 1897 listMenuPresenter = null; 1898 } 1899 1900 void setStyle(Context context) { 1901 final TypedValue outValue = new TypedValue(); 1902 final Resources.Theme widgetTheme = context.getResources().newTheme(); 1903 widgetTheme.setTo(context.getTheme()); 1904 1905 // First apply the actionBarPopupTheme 1906 widgetTheme.resolveAttribute(R.attr.actionBarPopupTheme, outValue, true); 1907 if (outValue.resourceId != 0) { 1908 widgetTheme.applyStyle(outValue.resourceId, true); 1909 } 1910 1911 // Now apply the panelMenuListTheme 1912 widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true); 1913 if (outValue.resourceId != 0) { 1914 widgetTheme.applyStyle(outValue.resourceId, true); 1915 } else { 1916 widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true); 1917 } 1918 1919 context = new ContextThemeWrapper(context, 0); 1920 context.getTheme().setTo(widgetTheme); 1921 1922 listPresenterContext = context; 1923 1924 TypedArray a = context.obtainStyledAttributes(R.styleable.AppCompatTheme); 1925 background = a.getResourceId( 1926 R.styleable.AppCompatTheme_panelBackground, 0); 1927 windowAnimations = a.getResourceId( 1928 R.styleable.AppCompatTheme_android_windowAnimationStyle, 0); 1929 a.recycle(); 1930 } 1931 1932 void setMenu(MenuBuilder menu) { 1933 if (menu == this.menu) return; 1934 1935 if (this.menu != null) { 1936 this.menu.removeMenuPresenter(listMenuPresenter); 1937 } 1938 this.menu = menu; 1939 if (menu != null) { 1940 if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter); 1941 } 1942 } 1943 1944 MenuView getListMenuView(MenuPresenter.Callback cb) { 1945 if (menu == null) return null; 1946 1947 if (listMenuPresenter == null) { 1948 listMenuPresenter = new ListMenuPresenter(listPresenterContext, 1949 R.layout.abc_list_menu_item_layout); 1950 listMenuPresenter.setCallback(cb); 1951 menu.addMenuPresenter(listMenuPresenter); 1952 } 1953 1954 MenuView result = listMenuPresenter.getMenuView(decorView); 1955 1956 return result; 1957 } 1958 1959 Parcelable onSaveInstanceState() { 1960 SavedState savedState = new SavedState(); 1961 savedState.featureId = featureId; 1962 savedState.isOpen = isOpen; 1963 1964 if (menu != null) { 1965 savedState.menuState = new Bundle(); 1966 menu.savePresenterStates(savedState.menuState); 1967 } 1968 1969 return savedState; 1970 } 1971 1972 void onRestoreInstanceState(Parcelable state) { 1973 SavedState savedState = (SavedState) state; 1974 featureId = savedState.featureId; 1975 wasLastOpen = savedState.isOpen; 1976 frozenMenuState = savedState.menuState; 1977 1978 shownPanelView = null; 1979 decorView = null; 1980 } 1981 1982 void applyFrozenState() { 1983 if (menu != null && frozenMenuState != null) { 1984 menu.restorePresenterStates(frozenMenuState); 1985 frozenMenuState = null; 1986 } 1987 } 1988 1989 private static class SavedState implements Parcelable { 1990 int featureId; 1991 boolean isOpen; 1992 Bundle menuState; 1993 1994 public int describeContents() { 1995 return 0; 1996 } 1997 1998 public void writeToParcel(Parcel dest, int flags) { 1999 dest.writeInt(featureId); 2000 dest.writeInt(isOpen ? 1 : 0); 2001 2002 if (isOpen) { 2003 dest.writeBundle(menuState); 2004 } 2005 } 2006 2007 private static SavedState readFromParcel(Parcel source, ClassLoader loader) { 2008 SavedState savedState = new SavedState(); 2009 savedState.featureId = source.readInt(); 2010 savedState.isOpen = source.readInt() == 1; 2011 2012 if (savedState.isOpen) { 2013 savedState.menuState = source.readBundle(loader); 2014 } 2015 2016 return savedState; 2017 } 2018 2019 public static final Parcelable.Creator<SavedState> CREATOR 2020 = ParcelableCompat.newCreator( 2021 new ParcelableCompatCreatorCallbacks<SavedState>() { 2022 @Override 2023 public SavedState createFromParcel(Parcel in, ClassLoader loader) { 2024 return readFromParcel(in, loader); 2025 } 2026 2027 @Override 2028 public SavedState[] newArray(int size) { 2029 return new SavedState[size]; 2030 } 2031 }); 2032 } 2033 } 2034 2035 private class ListMenuDecorView extends ContentFrameLayout { 2036 public ListMenuDecorView(Context context) { 2037 super(context); 2038 } 2039 2040 @Override 2041 public boolean dispatchKeyEvent(KeyEvent event) { 2042 return AppCompatDelegateImplV7.this.dispatchKeyEvent(event) 2043 || super.dispatchKeyEvent(event); 2044 } 2045 2046 @Override 2047 public boolean onInterceptTouchEvent(MotionEvent event) { 2048 int action = event.getAction(); 2049 if (action == MotionEvent.ACTION_DOWN) { 2050 int x = (int) event.getX(); 2051 int y = (int) event.getY(); 2052 if (isOutOfBounds(x, y)) { 2053 closePanel(Window.FEATURE_OPTIONS_PANEL); 2054 return true; 2055 } 2056 } 2057 return super.onInterceptTouchEvent(event); 2058 } 2059 2060 @Override 2061 public void setBackgroundResource(int resid) { 2062 setBackgroundDrawable(AppCompatDrawableManager.get().getDrawable(getContext(), resid)); 2063 } 2064 2065 private boolean isOutOfBounds(int x, int y) { 2066 return x < -5 || y < -5 || x > (getWidth() + 5) || y > (getHeight() + 5); 2067 } 2068 } 2069} 2070