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