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