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