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