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