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