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