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