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