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