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