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