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