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