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