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(@NonNull final 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(@NonNull ActionMode.Callback callback) { 712 endOnGoingFadeAnimation(); 713 if (mActionMode != null) { 714 mActionMode.finish(); 715 } 716 717 if (!(callback instanceof ActionModeCallbackWrapperV7)) { 718 // If the callback hasn't been wrapped yet, wrap it 719 callback = new ActionModeCallbackWrapperV7(callback); 720 } 721 722 ActionMode mode = null; 723 if (mAppCompatCallback != null && !isDestroyed()) { 724 try { 725 mode = mAppCompatCallback.onWindowStartingSupportActionMode(callback); 726 } catch (AbstractMethodError ame) { 727 // Older apps might not implement this callback method. 728 } 729 } 730 731 if (mode != null) { 732 mActionMode = mode; 733 } else { 734 if (mActionModeView == null) { 735 if (mIsFloating) { 736 // Use the action bar theme. 737 final TypedValue outValue = new TypedValue(); 738 final Resources.Theme baseTheme = mContext.getTheme(); 739 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); 740 741 final Context actionBarContext; 742 if (outValue.resourceId != 0) { 743 final Resources.Theme actionBarTheme = mContext.getResources().newTheme(); 744 actionBarTheme.setTo(baseTheme); 745 actionBarTheme.applyStyle(outValue.resourceId, true); 746 747 actionBarContext = new ContextThemeWrapper(mContext, 0); 748 actionBarContext.getTheme().setTo(actionBarTheme); 749 } else { 750 actionBarContext = mContext; 751 } 752 753 mActionModeView = new ActionBarContextView(actionBarContext); 754 mActionModePopup = new PopupWindow(actionBarContext, null, 755 R.attr.actionModePopupWindowStyle); 756 PopupWindowCompat.setWindowLayoutType(mActionModePopup, 757 WindowManager.LayoutParams.TYPE_APPLICATION); 758 mActionModePopup.setContentView(mActionModeView); 759 mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); 760 761 actionBarContext.getTheme().resolveAttribute( 762 R.attr.actionBarSize, outValue, true); 763 final int height = TypedValue.complexToDimensionPixelSize(outValue.data, 764 actionBarContext.getResources().getDisplayMetrics()); 765 mActionModeView.setContentHeight(height); 766 mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); 767 mShowActionModePopup = new Runnable() { 768 public void run() { 769 mActionModePopup.showAtLocation( 770 mActionModeView, 771 Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); 772 endOnGoingFadeAnimation(); 773 774 if (shouldAnimateActionModeView()) { 775 ViewCompat.setAlpha(mActionModeView, 0f); 776 mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f); 777 mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() { 778 @Override 779 public void onAnimationStart(View view) { 780 mActionModeView.setVisibility(View.VISIBLE); 781 } 782 783 @Override 784 public void onAnimationEnd(View view) { 785 ViewCompat.setAlpha(mActionModeView, 1f); 786 mFadeAnim.setListener(null); 787 mFadeAnim = null; 788 } 789 }); 790 } else { 791 ViewCompat.setAlpha(mActionModeView, 1f); 792 mActionModeView.setVisibility(View.VISIBLE); 793 } 794 } 795 }; 796 } else { 797 ViewStubCompat stub = (ViewStubCompat) mSubDecor 798 .findViewById(R.id.action_mode_bar_stub); 799 if (stub != null) { 800 // Set the layout inflater so that it is inflated with the action bar's context 801 stub.setLayoutInflater(LayoutInflater.from(getActionBarThemedContext())); 802 mActionModeView = (ActionBarContextView) stub.inflate(); 803 } 804 } 805 } 806 807 if (mActionModeView != null) { 808 endOnGoingFadeAnimation(); 809 mActionModeView.killMode(); 810 mode = new StandaloneActionMode(mActionModeView.getContext(), mActionModeView, 811 callback, mActionModePopup == null); 812 if (callback.onCreateActionMode(mode, mode.getMenu())) { 813 mode.invalidate(); 814 mActionModeView.initForMode(mode); 815 mActionMode = mode; 816 817 if (shouldAnimateActionModeView()) { 818 ViewCompat.setAlpha(mActionModeView, 0f); 819 mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f); 820 mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() { 821 @Override 822 public void onAnimationStart(View view) { 823 mActionModeView.setVisibility(View.VISIBLE); 824 mActionModeView.sendAccessibilityEvent( 825 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 826 if (mActionModeView.getParent() != null) { 827 ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); 828 } 829 } 830 831 @Override 832 public void onAnimationEnd(View view) { 833 ViewCompat.setAlpha(mActionModeView, 1f); 834 mFadeAnim.setListener(null); 835 mFadeAnim = null; 836 } 837 }); 838 } else { 839 ViewCompat.setAlpha(mActionModeView, 1f); 840 mActionModeView.setVisibility(View.VISIBLE); 841 mActionModeView.sendAccessibilityEvent( 842 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 843 if (mActionModeView.getParent() != null) { 844 ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); 845 } 846 } 847 848 if (mActionModePopup != null) { 849 mWindow.getDecorView().post(mShowActionModePopup); 850 } 851 } else { 852 mActionMode = null; 853 } 854 } 855 } 856 if (mActionMode != null && mAppCompatCallback != null) { 857 mAppCompatCallback.onSupportActionModeStarted(mActionMode); 858 } 859 return mActionMode; 860 } 861 862 final boolean shouldAnimateActionModeView() { 863 // We only to animate the action mode in if the sub decor has already been laid out. 864 // If it hasn't been laid out, it hasn't been drawn to screen yet. 865 return mSubDecorInstalled && mSubDecor != null && ViewCompat.isLaidOut(mSubDecor); 866 } 867 868 private void endOnGoingFadeAnimation() { 869 if (mFadeAnim != null) { 870 mFadeAnim.cancel(); 871 } 872 } 873 874 boolean onBackPressed() { 875 // Back cancels action modes first. 876 if (mActionMode != null) { 877 mActionMode.finish(); 878 return true; 879 } 880 881 // Next collapse any expanded action views. 882 ActionBar ab = getSupportActionBar(); 883 if (ab != null && ab.collapseActionView()) { 884 return true; 885 } 886 887 // Let the call through... 888 return false; 889 } 890 891 @Override 892 boolean onKeyShortcut(int keyCode, KeyEvent ev) { 893 // Let the Action Bar have a chance at handling the shortcut 894 ActionBar ab = getSupportActionBar(); 895 if (ab != null && ab.onKeyShortcut(keyCode, ev)) { 896 return true; 897 } 898 899 // If the panel is already prepared, then perform the shortcut using it. 900 boolean handled; 901 if (mPreparedPanel != null) { 902 handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev, 903 Menu.FLAG_PERFORM_NO_CLOSE); 904 if (handled) { 905 if (mPreparedPanel != null) { 906 mPreparedPanel.isHandled = true; 907 } 908 return true; 909 } 910 } 911 912 // If the panel is not prepared, then we may be trying to handle a shortcut key 913 // combination such as Control+C. Temporarily prepare the panel then mark it 914 // unprepared again when finished to ensure that the panel will again be prepared 915 // the next time it is shown for real. 916 if (mPreparedPanel == null) { 917 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 918 preparePanel(st, ev); 919 handled = performPanelShortcut(st, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE); 920 st.isPrepared = false; 921 if (handled) { 922 return true; 923 } 924 } 925 return false; 926 } 927 928 @Override 929 boolean dispatchKeyEvent(KeyEvent event) { 930 if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) { 931 // If this is a MENU event, let the Activity have a go. 932 if (mOriginalWindowCallback.dispatchKeyEvent(event)) { 933 return true; 934 } 935 } 936 937 final int keyCode = event.getKeyCode(); 938 final int action = event.getAction(); 939 final boolean isDown = action == KeyEvent.ACTION_DOWN; 940 941 return isDown ? onKeyDown(keyCode, event) : onKeyUp(keyCode, event); 942 } 943 944 boolean onKeyUp(int keyCode, KeyEvent event) { 945 switch (keyCode) { 946 case KeyEvent.KEYCODE_MENU: 947 onKeyUpPanel(Window.FEATURE_OPTIONS_PANEL, event); 948 return true; 949 case KeyEvent.KEYCODE_BACK: 950 final boolean wasLongPressBackDown = mLongPressBackDown; 951 mLongPressBackDown = false; 952 953 PanelFeatureState st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 954 if (st != null && st.isOpen) { 955 if (!wasLongPressBackDown) { 956 // Certain devices allow opening the options menu via a long press of the 957 // back button. We should only close the open options menu if it wasn't 958 // opened via a long press gesture. 959 closePanel(st, true); 960 } 961 return true; 962 } 963 if (onBackPressed()) { 964 return true; 965 } 966 break; 967 } 968 return false; 969 } 970 971 boolean onKeyDown(int keyCode, KeyEvent event) { 972 switch (keyCode) { 973 case KeyEvent.KEYCODE_MENU: 974 onKeyDownPanel(Window.FEATURE_OPTIONS_PANEL, event); 975 // We need to return true here and not let it bubble up to the Window. 976 // For empty menus, PhoneWindow's KEYCODE_BACK handling will steals all events, 977 // not allowing the Activity to call onBackPressed(). 978 return true; 979 case KeyEvent.KEYCODE_BACK: 980 // Certain devices allow opening the options menu via a long press of the back 981 // button. We keep a record of whether the last event is from a long press. 982 mLongPressBackDown = (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0; 983 break; 984 } 985 986 // On API v7-10 we need to manually call onKeyShortcut() as this is not called 987 // from the Activity 988 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { 989 // We do not return true here otherwise dispatchKeyEvent will not reach the Activity 990 // (which results in the back button not working) 991 onKeyShortcut(keyCode, event); 992 } 993 return false; 994 } 995 996 @Override 997 public View createView(View parent, final String name, @NonNull Context context, 998 @NonNull AttributeSet attrs) { 999 final boolean isPre21 = Build.VERSION.SDK_INT < 21; 1000 1001 if (mAppCompatViewInflater == null) { 1002 mAppCompatViewInflater = new AppCompatViewInflater(); 1003 } 1004 1005 // We only want the View to inherit its context if we're running pre-v21 1006 final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent); 1007 1008 return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, 1009 isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */ 1010 true, /* Read read app:theme as a fallback at all times for legacy reasons */ 1011 VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */ 1012 ); 1013 } 1014 1015 private boolean shouldInheritContext(ViewParent parent) { 1016 if (parent == null) { 1017 // The initial parent is null so just return false 1018 return false; 1019 } 1020 final View windowDecor = mWindow.getDecorView(); 1021 while (true) { 1022 if (parent == null) { 1023 // Bingo. We've hit a view which has a null parent before being terminated from 1024 // the loop. This is (most probably) because it's the root view in an inflation 1025 // call, therefore we should inherit. This works as the inflated layout is only 1026 // added to the hierarchy at the end of the inflate() call. 1027 return true; 1028 } else if (parent == windowDecor || !(parent instanceof View) 1029 || ViewCompat.isAttachedToWindow((View) parent)) { 1030 // We have either hit the window's decor view, a parent which isn't a View 1031 // (i.e. ViewRootImpl), or an attached view, so we know that the original parent 1032 // is currently added to the view hierarchy. This means that it has not be 1033 // inflated in the current inflate() call and we should not inherit the context. 1034 return false; 1035 } 1036 parent = parent.getParent(); 1037 } 1038 } 1039 1040 @Override 1041 public void installViewFactory() { 1042 LayoutInflater layoutInflater = LayoutInflater.from(mContext); 1043 if (layoutInflater.getFactory() == null) { 1044 LayoutInflaterCompat.setFactory(layoutInflater, this); 1045 } else { 1046 if (!(LayoutInflaterCompat.getFactory(layoutInflater) 1047 instanceof AppCompatDelegateImplV7)) { 1048 Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" 1049 + " so we can not install AppCompat's"); 1050 } 1051 } 1052 } 1053 1054 /** 1055 * From {@link android.support.v4.view.LayoutInflaterFactory} 1056 */ 1057 @Override 1058 public final View onCreateView(View parent, String name, 1059 Context context, AttributeSet attrs) { 1060 // First let the Activity's Factory try and inflate the view 1061 final View view = callActivityOnCreateView(parent, name, context, attrs); 1062 if (view != null) { 1063 return view; 1064 } 1065 1066 // If the Factory didn't handle it, let our createView() method try 1067 return createView(parent, name, context, attrs); 1068 } 1069 1070 View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) { 1071 // Let the Activity's LayoutInflater.Factory try and handle it 1072 if (mOriginalWindowCallback instanceof LayoutInflater.Factory) { 1073 final View result = ((LayoutInflater.Factory) mOriginalWindowCallback) 1074 .onCreateView(name, context, attrs); 1075 if (result != null) { 1076 return result; 1077 } 1078 } 1079 return null; 1080 } 1081 1082 private void openPanel(final PanelFeatureState st, KeyEvent event) { 1083 // Already open, return 1084 if (st.isOpen || isDestroyed()) { 1085 return; 1086 } 1087 1088 // Don't open an options panel for honeycomb apps on xlarge devices. 1089 // (The app should be using an action bar for menu items.) 1090 if (st.featureId == FEATURE_OPTIONS_PANEL) { 1091 Context context = mContext; 1092 Configuration config = context.getResources().getConfiguration(); 1093 boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == 1094 Configuration.SCREENLAYOUT_SIZE_XLARGE; 1095 boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >= 1096 android.os.Build.VERSION_CODES.HONEYCOMB; 1097 1098 if (isXLarge && isHoneycombApp) { 1099 return; 1100 } 1101 } 1102 1103 Window.Callback cb = getWindowCallback(); 1104 if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) { 1105 // Callback doesn't want the menu to open, reset any state 1106 closePanel(st, true); 1107 return; 1108 } 1109 1110 final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 1111 if (wm == null) { 1112 return; 1113 } 1114 1115 // Prepare panel (should have been done before, but just in case) 1116 if (!preparePanel(st, event)) { 1117 return; 1118 } 1119 1120 int width = WRAP_CONTENT; 1121 if (st.decorView == null || st.refreshDecorView) { 1122 if (st.decorView == null) { 1123 // Initialize the panel decor, this will populate st.decorView 1124 if (!initializePanelDecor(st) || (st.decorView == null)) 1125 return; 1126 } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) { 1127 // Decor needs refreshing, so remove its views 1128 st.decorView.removeAllViews(); 1129 } 1130 1131 // This will populate st.shownPanelView 1132 if (!initializePanelContent(st) || !st.hasPanelItems()) { 1133 return; 1134 } 1135 1136 ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams(); 1137 if (lp == null) { 1138 lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); 1139 } 1140 1141 int backgroundResId = st.background; 1142 st.decorView.setBackgroundResource(backgroundResId); 1143 1144 ViewParent shownPanelParent = st.shownPanelView.getParent(); 1145 if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) { 1146 ((ViewGroup) shownPanelParent).removeView(st.shownPanelView); 1147 } 1148 st.decorView.addView(st.shownPanelView, lp); 1149 1150 /* 1151 * Give focus to the view, if it or one of its children does not 1152 * already have it. 1153 */ 1154 if (!st.shownPanelView.hasFocus()) { 1155 st.shownPanelView.requestFocus(); 1156 } 1157 } else if (st.createdPanelView != null) { 1158 // If we already had a panel view, carry width=MATCH_PARENT through 1159 // as we did above when it was created. 1160 ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams(); 1161 if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) { 1162 width = MATCH_PARENT; 1163 } 1164 } 1165 1166 st.isHandled = false; 1167 1168 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 1169 width, WRAP_CONTENT, 1170 st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL, 1171 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 1172 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 1173 PixelFormat.TRANSLUCENT); 1174 1175 lp.gravity = st.gravity; 1176 lp.windowAnimations = st.windowAnimations; 1177 1178 wm.addView(st.decorView, lp); 1179 st.isOpen = true; 1180 } 1181 1182 private boolean initializePanelDecor(PanelFeatureState st) { 1183 st.setStyle(getActionBarThemedContext()); 1184 st.decorView = new ListMenuDecorView(st.listPresenterContext); 1185 st.gravity = Gravity.CENTER | Gravity.BOTTOM; 1186 return true; 1187 } 1188 1189 private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) { 1190 if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() && 1191 (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext)) || 1192 mDecorContentParent.isOverflowMenuShowPending())) { 1193 1194 final Window.Callback cb = getWindowCallback(); 1195 1196 if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) { 1197 if (cb != null && !isDestroyed()) { 1198 // If we have a menu invalidation pending, do it now. 1199 if (mInvalidatePanelMenuPosted && 1200 (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) { 1201 mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable); 1202 mInvalidatePanelMenuRunnable.run(); 1203 } 1204 1205 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1206 1207 // If we don't have a menu or we're waiting for a full content refresh, 1208 // forget it. This is a lingering event that no longer matters. 1209 if (st.menu != null && !st.refreshMenuContent && 1210 cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { 1211 cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, st.menu); 1212 mDecorContentParent.showOverflowMenu(); 1213 } 1214 } 1215 } else { 1216 mDecorContentParent.hideOverflowMenu(); 1217 if (!isDestroyed()) { 1218 final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1219 cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, st.menu); 1220 } 1221 } 1222 return; 1223 } 1224 1225 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); 1226 1227 st.refreshDecorView = true; 1228 closePanel(st, false); 1229 1230 openPanel(st, null); 1231 } 1232 1233 private boolean initializePanelMenu(final PanelFeatureState st) { 1234 Context context = mContext; 1235 1236 // If we have an action bar, initialize the menu with the right theme. 1237 if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR) && 1238 mDecorContentParent != null) { 1239 final TypedValue outValue = new TypedValue(); 1240 final Resources.Theme baseTheme = context.getTheme(); 1241 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); 1242 1243 Resources.Theme widgetTheme = null; 1244 if (outValue.resourceId != 0) { 1245 widgetTheme = context.getResources().newTheme(); 1246 widgetTheme.setTo(baseTheme); 1247 widgetTheme.applyStyle(outValue.resourceId, true); 1248 widgetTheme.resolveAttribute( 1249 R.attr.actionBarWidgetTheme, outValue, true); 1250 } else { 1251 baseTheme.resolveAttribute( 1252 R.attr.actionBarWidgetTheme, outValue, true); 1253 } 1254 1255 if (outValue.resourceId != 0) { 1256 if (widgetTheme == null) { 1257 widgetTheme = context.getResources().newTheme(); 1258 widgetTheme.setTo(baseTheme); 1259 } 1260 widgetTheme.applyStyle(outValue.resourceId, true); 1261 } 1262 1263 if (widgetTheme != null) { 1264 context = new ContextThemeWrapper(context, 0); 1265 context.getTheme().setTo(widgetTheme); 1266 } 1267 } 1268 1269 final MenuBuilder menu = new MenuBuilder(context); 1270 menu.setCallback(this); 1271 st.setMenu(menu); 1272 1273 return true; 1274 } 1275 1276 private boolean initializePanelContent(PanelFeatureState st) { 1277 if (st.createdPanelView != null) { 1278 st.shownPanelView = st.createdPanelView; 1279 return true; 1280 } 1281 1282 if (st.menu == null) { 1283 return false; 1284 } 1285 1286 if (mPanelMenuPresenterCallback == null) { 1287 mPanelMenuPresenterCallback = new PanelMenuPresenterCallback(); 1288 } 1289 1290 MenuView menuView = st.getListMenuView(mPanelMenuPresenterCallback); 1291 1292 st.shownPanelView = (View) menuView; 1293 1294 return st.shownPanelView != null; 1295 } 1296 1297 private boolean preparePanel(PanelFeatureState st, KeyEvent event) { 1298 if (isDestroyed()) { 1299 return false; 1300 } 1301 1302 // Already prepared (isPrepared will be reset to false later) 1303 if (st.isPrepared) { 1304 return true; 1305 } 1306 1307 if ((mPreparedPanel != null) && (mPreparedPanel != st)) { 1308 // Another Panel is prepared and possibly open, so close it 1309 closePanel(mPreparedPanel, false); 1310 } 1311 1312 final Window.Callback cb = getWindowCallback(); 1313 1314 if (cb != null) { 1315 st.createdPanelView = cb.onCreatePanelView(st.featureId); 1316 } 1317 1318 final boolean isActionBarMenu = 1319 (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_SUPPORT_ACTION_BAR); 1320 1321 if (isActionBarMenu && mDecorContentParent != null) { 1322 // Enforce ordering guarantees around events so that the action bar never 1323 // dispatches menu-related events before the panel is prepared. 1324 mDecorContentParent.setMenuPrepared(); 1325 } 1326 1327 if (st.createdPanelView == null && 1328 (!isActionBarMenu || !(peekSupportActionBar() instanceof ToolbarActionBar))) { 1329 // Since ToolbarActionBar handles the list options menu itself, we only want to 1330 // init this menu panel if we're not using a TAB. 1331 if (st.menu == null || st.refreshMenuContent) { 1332 if (st.menu == null) { 1333 if (!initializePanelMenu(st) || (st.menu == null)) { 1334 return false; 1335 } 1336 } 1337 1338 if (isActionBarMenu && mDecorContentParent != null) { 1339 if (mActionMenuPresenterCallback == null) { 1340 mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); 1341 } 1342 mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback); 1343 } 1344 1345 // Creating the panel menu will involve a lot of manipulation; 1346 // don't dispatch change events to presenters until we're done. 1347 st.menu.stopDispatchingItemsChanged(); 1348 if (!cb.onCreatePanelMenu(st.featureId, st.menu)) { 1349 // Ditch the menu created above 1350 st.setMenu(null); 1351 1352 if (isActionBarMenu && mDecorContentParent != null) { 1353 // Don't show it in the action bar either 1354 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 1355 } 1356 1357 return false; 1358 } 1359 1360 st.refreshMenuContent = false; 1361 } 1362 1363 // Preparing the panel menu can involve a lot of manipulation; 1364 // don't dispatch change events to presenters until we're done. 1365 st.menu.stopDispatchingItemsChanged(); 1366 1367 // Restore action view state before we prepare. This gives apps 1368 // an opportunity to override frozen/restored state in onPrepare. 1369 if (st.frozenActionViewState != null) { 1370 st.menu.restoreActionViewStates(st.frozenActionViewState); 1371 st.frozenActionViewState = null; 1372 } 1373 1374 // Callback and return if the callback does not want to show the menu 1375 if (!cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) { 1376 if (isActionBarMenu && mDecorContentParent != null) { 1377 // The app didn't want to show the menu for now but it still exists. 1378 // Clear it out of the action bar. 1379 mDecorContentParent.setMenu(null, mActionMenuPresenterCallback); 1380 } 1381 st.menu.startDispatchingItemsChanged(); 1382 return false; 1383 } 1384 1385 // Set the proper keymap 1386 KeyCharacterMap kmap = KeyCharacterMap.load( 1387 event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD); 1388 st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC; 1389 st.menu.setQwertyMode(st.qwertyMode); 1390 st.menu.startDispatchingItemsChanged(); 1391 } 1392 1393 // Set other state 1394 st.isPrepared = true; 1395 st.isHandled = false; 1396 mPreparedPanel = st; 1397 1398 return true; 1399 } 1400 1401 private void checkCloseActionMenu(MenuBuilder menu) { 1402 if (mClosingActionMenu) { 1403 return; 1404 } 1405 1406 mClosingActionMenu = true; 1407 mDecorContentParent.dismissPopups(); 1408 Window.Callback cb = getWindowCallback(); 1409 if (cb != null && !isDestroyed()) { 1410 cb.onPanelClosed(FEATURE_SUPPORT_ACTION_BAR, menu); 1411 } 1412 mClosingActionMenu = false; 1413 } 1414 1415 private void closePanel(int featureId) { 1416 closePanel(getPanelState(featureId, true), true); 1417 } 1418 1419 private void closePanel(PanelFeatureState st, boolean doCallback) { 1420 if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL && 1421 mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) { 1422 checkCloseActionMenu(st.menu); 1423 return; 1424 } 1425 1426 final WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 1427 if (wm != null && st.isOpen && st.decorView != null) { 1428 wm.removeView(st.decorView); 1429 1430 if (doCallback) { 1431 callOnPanelClosed(st.featureId, st, null); 1432 } 1433 } 1434 1435 st.isPrepared = false; 1436 st.isHandled = false; 1437 st.isOpen = false; 1438 1439 // This view is no longer shown, so null it out 1440 st.shownPanelView = null; 1441 1442 // Next time the menu opens, it should not be in expanded mode, so 1443 // force a refresh of the decor 1444 st.refreshDecorView = true; 1445 1446 if (mPreparedPanel == st) { 1447 mPreparedPanel = null; 1448 } 1449 } 1450 1451 private boolean onKeyDownPanel(int featureId, KeyEvent event) { 1452 if (event.getRepeatCount() == 0) { 1453 PanelFeatureState st = getPanelState(featureId, true); 1454 if (!st.isOpen) { 1455 return preparePanel(st, event); 1456 } 1457 } 1458 1459 return false; 1460 } 1461 1462 private boolean onKeyUpPanel(int featureId, KeyEvent event) { 1463 if (mActionMode != null) { 1464 return false; 1465 } 1466 1467 boolean handled = false; 1468 final PanelFeatureState st = getPanelState(featureId, true); 1469 if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null && 1470 mDecorContentParent.canShowOverflowMenu() && 1471 !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mContext))) { 1472 if (!mDecorContentParent.isOverflowMenuShowing()) { 1473 if (!isDestroyed() && preparePanel(st, event)) { 1474 handled = mDecorContentParent.showOverflowMenu(); 1475 } 1476 } else { 1477 handled = mDecorContentParent.hideOverflowMenu(); 1478 } 1479 } else { 1480 if (st.isOpen || st.isHandled) { 1481 // Play the sound effect if the user closed an open menu (and not if 1482 // they just released a menu shortcut) 1483 handled = st.isOpen; 1484 // Close menu 1485 closePanel(st, true); 1486 } else if (st.isPrepared) { 1487 boolean show = true; 1488 if (st.refreshMenuContent) { 1489 // Something may have invalidated the menu since we prepared it. 1490 // Re-prepare it to refresh. 1491 st.isPrepared = false; 1492 show = preparePanel(st, event); 1493 } 1494 1495 if (show) { 1496 // Show menu 1497 openPanel(st, event); 1498 handled = true; 1499 } 1500 } 1501 } 1502 1503 if (handled) { 1504 AudioManager audioManager = (AudioManager) mContext.getSystemService( 1505 Context.AUDIO_SERVICE); 1506 if (audioManager != null) { 1507 audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); 1508 } else { 1509 Log.w(TAG, "Couldn't get audio manager"); 1510 } 1511 } 1512 return handled; 1513 } 1514 1515 private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) { 1516 // Try to get a menu 1517 if (menu == null) { 1518 // Need a panel to grab the menu, so try to get that 1519 if (panel == null) { 1520 if ((featureId >= 0) && (featureId < mPanels.length)) { 1521 panel = mPanels[featureId]; 1522 } 1523 } 1524 1525 if (panel != null) { 1526 // menu still may be null, which is okay--we tried our best 1527 menu = panel.menu; 1528 } 1529 } 1530 1531 // If the panel is not open, do not callback 1532 if ((panel != null) && (!panel.isOpen)) 1533 return; 1534 1535 if (!isDestroyed()) { 1536 // We need to be careful which callback we dispatch the call to. We can not dispatch 1537 // this to the Window's callback since that will call back into this method and cause a 1538 // crash. Instead we need to dispatch down to the original Activity/Dialog/etc. 1539 mOriginalWindowCallback.onPanelClosed(featureId, menu); 1540 } 1541 } 1542 1543 private PanelFeatureState findMenuPanel(Menu menu) { 1544 final PanelFeatureState[] panels = mPanels; 1545 final int N = panels != null ? panels.length : 0; 1546 for (int i = 0; i < N; i++) { 1547 final PanelFeatureState panel = panels[i]; 1548 if (panel != null && panel.menu == menu) { 1549 return panel; 1550 } 1551 } 1552 return null; 1553 } 1554 1555 protected PanelFeatureState getPanelState(int featureId, boolean required) { 1556 PanelFeatureState[] ar; 1557 if ((ar = mPanels) == null || ar.length <= featureId) { 1558 PanelFeatureState[] nar = new PanelFeatureState[featureId + 1]; 1559 if (ar != null) { 1560 System.arraycopy(ar, 0, nar, 0, ar.length); 1561 } 1562 mPanels = ar = nar; 1563 } 1564 1565 PanelFeatureState st = ar[featureId]; 1566 if (st == null) { 1567 ar[featureId] = st = new PanelFeatureState(featureId); 1568 } 1569 return st; 1570 } 1571 1572 private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event, 1573 int flags) { 1574 if (event.isSystem()) { 1575 return false; 1576 } 1577 1578 boolean handled = false; 1579 1580 // Only try to perform menu shortcuts if preparePanel returned true (possible false 1581 // return value from application not wanting to show the menu). 1582 if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) { 1583 // The menu is prepared now, perform the shortcut on it 1584 handled = st.menu.performShortcut(keyCode, event, flags); 1585 } 1586 1587 if (handled) { 1588 // Only close down the menu if we don't have an action bar keeping it open. 1589 if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) { 1590 closePanel(st, true); 1591 } 1592 } 1593 1594 return handled; 1595 } 1596 1597 private void invalidatePanelMenu(int featureId) { 1598 mInvalidatePanelMenuFeatures |= 1 << featureId; 1599 1600 if (!mInvalidatePanelMenuPosted) { 1601 ViewCompat.postOnAnimation(mWindow.getDecorView(), mInvalidatePanelMenuRunnable); 1602 mInvalidatePanelMenuPosted = true; 1603 } 1604 } 1605 1606 private void doInvalidatePanelMenu(int featureId) { 1607 PanelFeatureState st = getPanelState(featureId, true); 1608 Bundle savedActionViewStates = null; 1609 if (st.menu != null) { 1610 savedActionViewStates = new Bundle(); 1611 st.menu.saveActionViewStates(savedActionViewStates); 1612 if (savedActionViewStates.size() > 0) { 1613 st.frozenActionViewState = savedActionViewStates; 1614 } 1615 // This will be started again when the panel is prepared. 1616 st.menu.stopDispatchingItemsChanged(); 1617 st.menu.clear(); 1618 } 1619 st.refreshMenuContent = true; 1620 st.refreshDecorView = true; 1621 1622 // Prepare the options panel if we have an action bar 1623 if ((featureId == FEATURE_SUPPORT_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL) 1624 && mDecorContentParent != null) { 1625 st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 1626 if (st != null) { 1627 st.isPrepared = false; 1628 preparePanel(st, null); 1629 } 1630 } 1631 } 1632 1633 /** 1634 * Updates the status bar guard 1635 * 1636 * @param insetTop the current top system window inset 1637 * @return the new top system window inset 1638 */ 1639 private int updateStatusGuard(int insetTop) { 1640 boolean showStatusGuard = false; 1641 // Show the status guard when the non-overlay contextual action bar is showing 1642 if (mActionModeView != null) { 1643 if (mActionModeView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { 1644 ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) 1645 mActionModeView.getLayoutParams(); 1646 boolean mlpChanged = false; 1647 1648 if (mActionModeView.isShown()) { 1649 if (mTempRect1 == null) { 1650 mTempRect1 = new Rect(); 1651 mTempRect2 = new Rect(); 1652 } 1653 final Rect insets = mTempRect1; 1654 final Rect localInsets = mTempRect2; 1655 insets.set(0, insetTop, 0, 0); 1656 1657 ViewUtils.computeFitSystemWindows(mSubDecor, insets, localInsets); 1658 final int newMargin = localInsets.top == 0 ? insetTop : 0; 1659 if (mlp.topMargin != newMargin) { 1660 mlpChanged = true; 1661 mlp.topMargin = insetTop; 1662 1663 if (mStatusGuard == null) { 1664 mStatusGuard = new View(mContext); 1665 mStatusGuard.setBackgroundColor(mContext.getResources() 1666 .getColor(R.color.abc_input_method_navigation_guard)); 1667 mSubDecor.addView(mStatusGuard, -1, 1668 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1669 insetTop)); 1670 } else { 1671 ViewGroup.LayoutParams lp = mStatusGuard.getLayoutParams(); 1672 if (lp.height != insetTop) { 1673 lp.height = insetTop; 1674 mStatusGuard.setLayoutParams(lp); 1675 } 1676 } 1677 } 1678 1679 // The action mode's theme may differ from the app, so 1680 // always show the status guard above it. 1681 showStatusGuard = mStatusGuard != null; 1682 1683 // We only need to consume the insets if the action 1684 // mode is overlaid on the app content (e.g. it's 1685 // sitting in a FrameLayout, see 1686 // screen_simple_overlay_action_mode.xml). 1687 if (!mOverlayActionMode && showStatusGuard) { 1688 insetTop = 0; 1689 } 1690 } else { 1691 // reset top margin 1692 if (mlp.topMargin != 0) { 1693 mlpChanged = true; 1694 mlp.topMargin = 0; 1695 } 1696 } 1697 if (mlpChanged) { 1698 mActionModeView.setLayoutParams(mlp); 1699 } 1700 } 1701 } 1702 if (mStatusGuard != null) { 1703 mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE); 1704 } 1705 1706 return insetTop; 1707 } 1708 1709 private void throwFeatureRequestIfSubDecorInstalled() { 1710 if (mSubDecorInstalled) { 1711 throw new AndroidRuntimeException( 1712 "Window feature must be requested before adding content"); 1713 } 1714 } 1715 1716 private int sanitizeWindowFeatureId(int featureId) { 1717 if (featureId == WindowCompat.FEATURE_ACTION_BAR) { 1718 Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR" 1719 + " id when requesting this feature."); 1720 return FEATURE_SUPPORT_ACTION_BAR; 1721 } else if (featureId == WindowCompat.FEATURE_ACTION_BAR_OVERLAY) { 1722 Log.i(TAG, "You should now use the AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR_OVERLAY" 1723 + " id when requesting this feature."); 1724 return FEATURE_SUPPORT_ACTION_BAR_OVERLAY; 1725 } 1726 // Else we'll just return the original id 1727 return featureId; 1728 } 1729 1730 ViewGroup getSubDecor() { 1731 return mSubDecor; 1732 } 1733 1734 private void dismissPopups() { 1735 if (mDecorContentParent != null) { 1736 mDecorContentParent.dismissPopups(); 1737 } 1738 1739 if (mActionModePopup != null) { 1740 mWindow.getDecorView().removeCallbacks(mShowActionModePopup); 1741 if (mActionModePopup.isShowing()) { 1742 try { 1743 mActionModePopup.dismiss(); 1744 } catch (IllegalArgumentException e) { 1745 // Pre-v18, there are times when the Window will remove the popup before us. 1746 // In these cases we need to swallow the resulting exception. 1747 } 1748 } 1749 mActionModePopup = null; 1750 } 1751 endOnGoingFadeAnimation(); 1752 1753 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); 1754 if (st != null && st.menu != null) { 1755 st.menu.close(); 1756 } 1757 } 1758 1759 /** 1760 * Clears out internal reference when the action mode is destroyed. 1761 */ 1762 class ActionModeCallbackWrapperV7 implements ActionMode.Callback { 1763 private ActionMode.Callback mWrapped; 1764 1765 public ActionModeCallbackWrapperV7(ActionMode.Callback wrapped) { 1766 mWrapped = wrapped; 1767 } 1768 1769 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 1770 return mWrapped.onCreateActionMode(mode, menu); 1771 } 1772 1773 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 1774 return mWrapped.onPrepareActionMode(mode, menu); 1775 } 1776 1777 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 1778 return mWrapped.onActionItemClicked(mode, item); 1779 } 1780 1781 public void onDestroyActionMode(ActionMode mode) { 1782 mWrapped.onDestroyActionMode(mode); 1783 if (mActionModePopup != null) { 1784 mWindow.getDecorView().removeCallbacks(mShowActionModePopup); 1785 } 1786 1787 if (mActionModeView != null) { 1788 endOnGoingFadeAnimation(); 1789 mFadeAnim = ViewCompat.animate(mActionModeView).alpha(0f); 1790 mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() { 1791 @Override 1792 public void onAnimationEnd(View view) { 1793 mActionModeView.setVisibility(View.GONE); 1794 if (mActionModePopup != null) { 1795 mActionModePopup.dismiss(); 1796 } else if (mActionModeView.getParent() instanceof View) { 1797 ViewCompat.requestApplyInsets((View) mActionModeView.getParent()); 1798 } 1799 mActionModeView.removeAllViews(); 1800 mFadeAnim.setListener(null); 1801 mFadeAnim = null; 1802 } 1803 }); 1804 } 1805 if (mAppCompatCallback != null) { 1806 mAppCompatCallback.onSupportActionModeFinished(mActionMode); 1807 } 1808 mActionMode = null; 1809 } 1810 } 1811 1812 private final class PanelMenuPresenterCallback implements MenuPresenter.Callback { 1813 @Override 1814 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1815 final Menu parentMenu = menu.getRootMenu(); 1816 final boolean isSubMenu = parentMenu != menu; 1817 final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu); 1818 if (panel != null) { 1819 if (isSubMenu) { 1820 callOnPanelClosed(panel.featureId, panel, parentMenu); 1821 closePanel(panel, true); 1822 } else { 1823 // Close the panel and only do the callback if the menu is being 1824 // closed completely, not if opening a sub menu 1825 closePanel(panel, allMenusAreClosing); 1826 } 1827 } 1828 } 1829 1830 @Override 1831 public boolean onOpenSubMenu(MenuBuilder subMenu) { 1832 if (subMenu == null && mHasActionBar) { 1833 Window.Callback cb = getWindowCallback(); 1834 if (cb != null && !isDestroyed()) { 1835 cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu); 1836 } 1837 } 1838 return true; 1839 } 1840 } 1841 1842 private final class ActionMenuPresenterCallback implements MenuPresenter.Callback { 1843 @Override 1844 public boolean onOpenSubMenu(MenuBuilder subMenu) { 1845 Window.Callback cb = getWindowCallback(); 1846 if (cb != null) { 1847 cb.onMenuOpened(FEATURE_SUPPORT_ACTION_BAR, subMenu); 1848 } 1849 return true; 1850 } 1851 1852 @Override 1853 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1854 checkCloseActionMenu(menu); 1855 } 1856 } 1857 1858 protected static final class PanelFeatureState { 1859 1860 /** Feature ID for this panel. */ 1861 int featureId; 1862 1863 int background; 1864 1865 int gravity; 1866 1867 int x; 1868 1869 int y; 1870 1871 int windowAnimations; 1872 1873 /** Dynamic state of the panel. */ 1874 ViewGroup decorView; 1875 1876 /** The panel that we are actually showing. */ 1877 View shownPanelView; 1878 1879 /** The panel that was returned by onCreatePanelView(). */ 1880 View createdPanelView; 1881 1882 /** Use {@link #setMenu} to set this. */ 1883 MenuBuilder menu; 1884 1885 ListMenuPresenter listMenuPresenter; 1886 1887 Context listPresenterContext; 1888 1889 /** 1890 * Whether the panel has been prepared (see 1891 * {@link #preparePanel}). 1892 */ 1893 boolean isPrepared; 1894 1895 /** 1896 * Whether an item's action has been performed. This happens in obvious 1897 * scenarios (user clicks on menu item), but can also happen with 1898 * chording menu+(shortcut key). 1899 */ 1900 boolean isHandled; 1901 1902 boolean isOpen; 1903 1904 public boolean qwertyMode; 1905 1906 boolean refreshDecorView; 1907 1908 boolean refreshMenuContent; 1909 1910 boolean wasLastOpen; 1911 1912 /** 1913 * Contains the state of the menu when told to freeze. 1914 */ 1915 Bundle frozenMenuState; 1916 1917 /** 1918 * Contains the state of associated action views when told to freeze. 1919 * These are saved across invalidations. 1920 */ 1921 Bundle frozenActionViewState; 1922 1923 PanelFeatureState(int featureId) { 1924 this.featureId = featureId; 1925 1926 refreshDecorView = false; 1927 } 1928 1929 public boolean hasPanelItems() { 1930 if (shownPanelView == null) return false; 1931 if (createdPanelView != null) return true; 1932 1933 return listMenuPresenter.getAdapter().getCount() > 0; 1934 } 1935 1936 /** 1937 * Unregister and free attached MenuPresenters. They will be recreated as needed. 1938 */ 1939 public void clearMenuPresenters() { 1940 if (menu != null) { 1941 menu.removeMenuPresenter(listMenuPresenter); 1942 } 1943 listMenuPresenter = null; 1944 } 1945 1946 void setStyle(Context context) { 1947 final TypedValue outValue = new TypedValue(); 1948 final Resources.Theme widgetTheme = context.getResources().newTheme(); 1949 widgetTheme.setTo(context.getTheme()); 1950 1951 // First apply the actionBarPopupTheme 1952 widgetTheme.resolveAttribute(R.attr.actionBarPopupTheme, outValue, true); 1953 if (outValue.resourceId != 0) { 1954 widgetTheme.applyStyle(outValue.resourceId, true); 1955 } 1956 1957 // Now apply the panelMenuListTheme 1958 widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true); 1959 if (outValue.resourceId != 0) { 1960 widgetTheme.applyStyle(outValue.resourceId, true); 1961 } else { 1962 widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true); 1963 } 1964 1965 context = new ContextThemeWrapper(context, 0); 1966 context.getTheme().setTo(widgetTheme); 1967 1968 listPresenterContext = context; 1969 1970 TypedArray a = context.obtainStyledAttributes(R.styleable.AppCompatTheme); 1971 background = a.getResourceId( 1972 R.styleable.AppCompatTheme_panelBackground, 0); 1973 windowAnimations = a.getResourceId( 1974 R.styleable.AppCompatTheme_android_windowAnimationStyle, 0); 1975 a.recycle(); 1976 } 1977 1978 void setMenu(MenuBuilder menu) { 1979 if (menu == this.menu) return; 1980 1981 if (this.menu != null) { 1982 this.menu.removeMenuPresenter(listMenuPresenter); 1983 } 1984 this.menu = menu; 1985 if (menu != null) { 1986 if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter); 1987 } 1988 } 1989 1990 MenuView getListMenuView(MenuPresenter.Callback cb) { 1991 if (menu == null) return null; 1992 1993 if (listMenuPresenter == null) { 1994 listMenuPresenter = new ListMenuPresenter(listPresenterContext, 1995 R.layout.abc_list_menu_item_layout); 1996 listMenuPresenter.setCallback(cb); 1997 menu.addMenuPresenter(listMenuPresenter); 1998 } 1999 2000 MenuView result = listMenuPresenter.getMenuView(decorView); 2001 2002 return result; 2003 } 2004 2005 Parcelable onSaveInstanceState() { 2006 SavedState savedState = new SavedState(); 2007 savedState.featureId = featureId; 2008 savedState.isOpen = isOpen; 2009 2010 if (menu != null) { 2011 savedState.menuState = new Bundle(); 2012 menu.savePresenterStates(savedState.menuState); 2013 } 2014 2015 return savedState; 2016 } 2017 2018 void onRestoreInstanceState(Parcelable state) { 2019 SavedState savedState = (SavedState) state; 2020 featureId = savedState.featureId; 2021 wasLastOpen = savedState.isOpen; 2022 frozenMenuState = savedState.menuState; 2023 2024 shownPanelView = null; 2025 decorView = null; 2026 } 2027 2028 void applyFrozenState() { 2029 if (menu != null && frozenMenuState != null) { 2030 menu.restorePresenterStates(frozenMenuState); 2031 frozenMenuState = null; 2032 } 2033 } 2034 2035 private static class SavedState implements Parcelable { 2036 int featureId; 2037 boolean isOpen; 2038 Bundle menuState; 2039 2040 public int describeContents() { 2041 return 0; 2042 } 2043 2044 public void writeToParcel(Parcel dest, int flags) { 2045 dest.writeInt(featureId); 2046 dest.writeInt(isOpen ? 1 : 0); 2047 2048 if (isOpen) { 2049 dest.writeBundle(menuState); 2050 } 2051 } 2052 2053 private static SavedState readFromParcel(Parcel source, ClassLoader loader) { 2054 SavedState savedState = new SavedState(); 2055 savedState.featureId = source.readInt(); 2056 savedState.isOpen = source.readInt() == 1; 2057 2058 if (savedState.isOpen) { 2059 savedState.menuState = source.readBundle(loader); 2060 } 2061 2062 return savedState; 2063 } 2064 2065 public static final Parcelable.Creator<SavedState> CREATOR 2066 = ParcelableCompat.newCreator( 2067 new ParcelableCompatCreatorCallbacks<SavedState>() { 2068 @Override 2069 public SavedState createFromParcel(Parcel in, ClassLoader loader) { 2070 return readFromParcel(in, loader); 2071 } 2072 2073 @Override 2074 public SavedState[] newArray(int size) { 2075 return new SavedState[size]; 2076 } 2077 }); 2078 } 2079 } 2080 2081 private class ListMenuDecorView extends ContentFrameLayout { 2082 public ListMenuDecorView(Context context) { 2083 super(context); 2084 } 2085 2086 @Override 2087 public boolean dispatchKeyEvent(KeyEvent event) { 2088 return AppCompatDelegateImplV7.this.dispatchKeyEvent(event) 2089 || super.dispatchKeyEvent(event); 2090 } 2091 2092 @Override 2093 public boolean onInterceptTouchEvent(MotionEvent event) { 2094 int action = event.getAction(); 2095 if (action == MotionEvent.ACTION_DOWN) { 2096 int x = (int) event.getX(); 2097 int y = (int) event.getY(); 2098 if (isOutOfBounds(x, y)) { 2099 closePanel(Window.FEATURE_OPTIONS_PANEL); 2100 return true; 2101 } 2102 } 2103 return super.onInterceptTouchEvent(event); 2104 } 2105 2106 @Override 2107 public void setBackgroundResource(int resid) { 2108 setBackgroundDrawable(AppCompatDrawableManager.get().getDrawable(getContext(), resid)); 2109 } 2110 2111 private boolean isOutOfBounds(int x, int y) { 2112 return x < -5 || y < -5 || x > (getWidth() + 5) || y > (getHeight() + 5); 2113 } 2114 } 2115} 2116