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