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