ActionBarActivityDelegateBase.java revision 8902df1bf0006a156503d40b1fc8a01f95d5b806
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.TypedArray; 22import android.graphics.drawable.Drawable; 23import android.os.Bundle; 24import android.support.v4.app.ActionBarDrawerToggle; 25import android.support.v4.view.WindowCompat; 26import android.support.v7.appcompat.R; 27import android.support.v7.internal.view.menu.ListMenuPresenter; 28import android.support.v7.internal.view.menu.MenuBuilder; 29import android.support.v7.internal.view.menu.MenuPresenter; 30import android.support.v7.internal.view.menu.MenuView; 31import android.support.v7.internal.view.menu.MenuWrapperFactory; 32import android.support.v7.internal.widget.ActionBarContainer; 33import android.support.v7.internal.widget.ActionBarContextView; 34import android.support.v7.internal.widget.ActionBarView; 35import android.support.v7.internal.widget.ProgressBarICS; 36import android.support.v7.view.ActionMode; 37import android.view.Menu; 38import android.view.MenuItem; 39import android.view.View; 40import android.view.ViewGroup; 41import android.view.Window; 42import android.widget.FrameLayout; 43 44class ActionBarActivityDelegateBase extends ActionBarActivityDelegate implements 45 MenuPresenter.Callback, MenuBuilder.Callback { 46 private static final String TAG = "ActionBarActivityDelegateBase"; 47 48 private static final int[] ACTION_BAR_DRAWABLE_TOGGLE_ATTRS = new int[] { 49 R.attr.homeAsUpIndicator 50 }; 51 52 private ActionBarView mActionBarView; 53 private ListMenuPresenter mListMenuPresenter; 54 private MenuBuilder mMenu; 55 56 private ActionMode mActionMode; 57 58 // true if we have installed a window sub-decor layout. 59 private boolean mSubDecorInstalled; 60 61 private CharSequence mTitleToSet; 62 63 // Used to keep track of Progress Bar Window features 64 private boolean mFeatureProgress, mFeatureIndeterminateProgress; 65 66 // Used for emulating PanelFeatureState 67 private boolean mClosingActionMenu; 68 private boolean mPanelIsPrepared; 69 private boolean mPanelRefreshContent; 70 private Bundle mPanelFrozenActionViewState; 71 72 ActionBarActivityDelegateBase(ActionBarActivity activity) { 73 super(activity); 74 } 75 76 @Override 77 public ActionBar createSupportActionBar() { 78 ensureSubDecor(); 79 return new ActionBarImplBase(mActivity, mActivity); 80 } 81 82 @Override 83 public void onConfigurationChanged(Configuration newConfig) { 84 // If this is called before sub-decor is installed, ActionBar will not 85 // be properly initialized. 86 if (mHasActionBar && mSubDecorInstalled) { 87 // Note: The action bar will need to access 88 // view changes from superclass. 89 ActionBarImplBase actionBar = (ActionBarImplBase) getSupportActionBar(); 90 actionBar.onConfigurationChanged(newConfig); 91 } 92 } 93 94 @Override 95 public void onStop() { 96 ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar(); 97 if (ab != null) { 98 ab.setShowHideAnimationEnabled(false); 99 } 100 } 101 102 @Override 103 public void onPostResume() { 104 ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar(); 105 if (ab != null) { 106 ab.setShowHideAnimationEnabled(true); 107 } 108 } 109 110 @Override 111 public void setContentView(View v) { 112 ensureSubDecor(); 113 if (mHasActionBar) { 114 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 115 contentParent.removeAllViews(); 116 contentParent.addView(v); 117 } else { 118 mActivity.superSetContentView(v); 119 } 120 mActivity.onSupportContentChanged(); 121 } 122 123 @Override 124 public void setContentView(int resId) { 125 ensureSubDecor(); 126 if (mHasActionBar) { 127 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 128 contentParent.removeAllViews(); 129 mActivity.getLayoutInflater().inflate(resId, contentParent); 130 } else { 131 mActivity.superSetContentView(resId); 132 } 133 mActivity.onSupportContentChanged(); 134 } 135 136 @Override 137 public void setContentView(View v, ViewGroup.LayoutParams lp) { 138 ensureSubDecor(); 139 if (mHasActionBar) { 140 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 141 contentParent.removeAllViews(); 142 contentParent.addView(v, lp); 143 } else { 144 mActivity.superSetContentView(v, lp); 145 } 146 mActivity.onSupportContentChanged(); 147 } 148 149 @Override 150 public void addContentView(View v, ViewGroup.LayoutParams lp) { 151 ensureSubDecor(); 152 if (mHasActionBar) { 153 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 154 contentParent.addView(v, lp); 155 } else { 156 mActivity.superSetContentView(v, lp); 157 } 158 mActivity.onSupportContentChanged(); 159 } 160 161 @Override 162 public void onContentChanged() { 163 // Ignore all calls to this method as we call onSupportContentChanged manually above 164 } 165 166 final void ensureSubDecor() { 167 if (mHasActionBar && !mSubDecorInstalled) { 168 if (mOverlayActionBar) { 169 mActivity.superSetContentView(R.layout.abc_action_bar_decor_overlay); 170 } else { 171 mActivity.superSetContentView(R.layout.abc_action_bar_decor); 172 } 173 mActionBarView = (ActionBarView) mActivity.findViewById(R.id.action_bar); 174 mActionBarView.setWindowCallback(mActivity); 175 176 /** 177 * Progress Bars 178 */ 179 if (mFeatureProgress) { 180 mActionBarView.initProgress(); 181 } 182 if (mFeatureIndeterminateProgress) { 183 mActionBarView.initIndeterminateProgress(); 184 } 185 186 /** 187 * Split Action Bar 188 */ 189 boolean splitWhenNarrow = UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW 190 .equals(getUiOptionsFromMetadata()); 191 boolean splitActionBar; 192 193 if (splitWhenNarrow) { 194 splitActionBar = mActivity.getResources() 195 .getBoolean(R.bool.abc_split_action_bar_is_narrow); 196 } else { 197 TypedArray a = mActivity.obtainStyledAttributes(R.styleable.ActionBarWindow); 198 splitActionBar = a 199 .getBoolean(R.styleable.ActionBarWindow_windowSplitActionBar, false); 200 a.recycle(); 201 } 202 203 final ActionBarContainer splitView = (ActionBarContainer) mActivity.findViewById( 204 R.id.split_action_bar); 205 if (splitView != null) { 206 mActionBarView.setSplitView(splitView); 207 mActionBarView.setSplitActionBar(splitActionBar); 208 mActionBarView.setSplitWhenNarrow(splitWhenNarrow); 209 210 final ActionBarContextView cab = (ActionBarContextView) mActivity.findViewById( 211 R.id.action_context_bar); 212 cab.setSplitView(splitView); 213 cab.setSplitActionBar(splitActionBar); 214 cab.setSplitWhenNarrow(splitWhenNarrow); 215 } 216 217 // Change our content FrameLayout to use the android.R.id.content id. 218 // Useful for fragments. 219 View content = mActivity.findViewById(android.R.id.content); 220 content.setId(View.NO_ID); 221 View abcContent = mActivity.findViewById(R.id.action_bar_activity_content); 222 abcContent.setId(android.R.id.content); 223 224 // A title was set before we've install the decor so set it now. 225 if (mTitleToSet != null) { 226 mActionBarView.setWindowTitle(mTitleToSet); 227 mTitleToSet = null; 228 } 229 230 mSubDecorInstalled = true; 231 232 // Post supportInvalidateOptionsMenu() so that the menu is invalidated post-onCreate() 233 content.post(new Runnable() { 234 @Override 235 public void run() { 236 supportInvalidateOptionsMenu(); 237 } 238 }); 239 } 240 } 241 242 @Override 243 public boolean supportRequestWindowFeature(int featureId) { 244 switch (featureId) { 245 case WindowCompat.FEATURE_ACTION_BAR: 246 mHasActionBar = true; 247 return true; 248 case WindowCompat.FEATURE_ACTION_BAR_OVERLAY: 249 mOverlayActionBar = true; 250 return true; 251 case Window.FEATURE_PROGRESS: 252 mFeatureProgress = true; 253 return true; 254 case Window.FEATURE_INDETERMINATE_PROGRESS: 255 mFeatureIndeterminateProgress = true; 256 return true; 257 default: 258 return mActivity.requestWindowFeature(featureId); 259 } 260 } 261 262 @Override 263 public void onTitleChanged(CharSequence title) { 264 if (mActionBarView != null) { 265 mActionBarView.setWindowTitle(title); 266 } else { 267 mTitleToSet = title; 268 } 269 } 270 271 @Override 272 public View onCreatePanelView(int featureId) { 273 View createdPanelView = null; 274 275 if (featureId == Window.FEATURE_OPTIONS_PANEL && preparePanel()) { 276 createdPanelView = (View) getListMenuView(mActivity, this); 277 } 278 279 return createdPanelView; 280 } 281 282 @Override 283 public boolean onCreatePanelMenu(int featureId, Menu menu) { 284 if (featureId != Window.FEATURE_OPTIONS_PANEL) { 285 return mActivity.superOnCreatePanelMenu(featureId, menu); 286 } 287 return false; 288 } 289 290 @Override 291 public boolean onPreparePanel(int featureId, View view, Menu menu) { 292 if (featureId != Window.FEATURE_OPTIONS_PANEL) { 293 return mActivity.superOnPreparePanel(featureId, view, menu); 294 } 295 return false; 296 } 297 298 @Override 299 public boolean onMenuItemSelected(int featureId, MenuItem item) { 300 if (featureId == Window.FEATURE_OPTIONS_PANEL) { 301 item = MenuWrapperFactory.createMenuItemWrapper(item); 302 } 303 return mActivity.superOnMenuItemSelected(featureId, item); 304 } 305 306 @Override 307 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 308 return mActivity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); 309 } 310 311 @Override 312 public void onMenuModeChange(MenuBuilder menu) { 313 reopenMenu(menu, true); 314 } 315 316 @Override 317 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 318 if (mClosingActionMenu) { 319 return; 320 } 321 mClosingActionMenu = true; 322 mActivity.closeOptionsMenu(); 323 mActionBarView.dismissPopupMenus(); 324 mClosingActionMenu = false; 325 } 326 327 @Override 328 public boolean onOpenSubMenu(MenuBuilder subMenu) { 329 return false; 330 } 331 332 @Override 333 public ActionMode startSupportActionMode(ActionMode.Callback callback) { 334 if (callback == null) { 335 throw new IllegalArgumentException("ActionMode callback can not be null."); 336 } 337 338 if (mActionMode != null) { 339 mActionMode.finish(); 340 } 341 342 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); 343 344 ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar(); 345 if (ab != null) { 346 mActionMode = ab.startActionMode(wrappedCallback); 347 } 348 349 if (mActionMode != null) { 350 mActivity.onSupportActionModeStarted(mActionMode); 351 } 352 return mActionMode; 353 } 354 355 @Override 356 public void supportInvalidateOptionsMenu() { 357 if (mMenu != null) { 358 Bundle savedActionViewStates = new Bundle(); 359 mMenu.saveActionViewStates(savedActionViewStates); 360 if (savedActionViewStates.size() > 0) { 361 mPanelFrozenActionViewState = savedActionViewStates; 362 } 363 // This will be started again when the panel is prepared. 364 mMenu.stopDispatchingItemsChanged(); 365 mMenu.clear(); 366 } 367 mPanelRefreshContent = true; 368 369 // Prepare the options panel if we have an action bar 370 if (mActionBarView != null) { 371 mPanelIsPrepared = false; 372 preparePanel(); 373 } 374 } 375 376 private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) { 377 if (mActionBarView != null && mActionBarView.isOverflowReserved()) { 378 if (!mActionBarView.isOverflowMenuShowing() || !toggleMenuMode) { 379 if (mActionBarView.getVisibility() == View.VISIBLE) { 380 mActionBarView.showOverflowMenu(); 381 } 382 } else { 383 mActionBarView.hideOverflowMenu(); 384 } 385 return; 386 } 387 388 menu.close(); 389 } 390 391 private MenuView getListMenuView(Context context, MenuPresenter.Callback cb) { 392 if (mMenu == null) { 393 return null; 394 } 395 396 if (mListMenuPresenter == null) { 397 TypedArray a = context.obtainStyledAttributes(R.styleable.Theme); 398 final int listPresenterTheme = a.getResourceId( 399 R.styleable.Theme_panelMenuListTheme, 400 R.style.Theme_AppCompat_CompactMenu); 401 a.recycle(); 402 403 mListMenuPresenter = new ListMenuPresenter( 404 R.layout.abc_list_menu_item_layout, listPresenterTheme); 405 mListMenuPresenter.setCallback(cb); 406 mMenu.addMenuPresenter(mListMenuPresenter); 407 } else { 408 // Make sure we update the ListView 409 mListMenuPresenter.updateMenuView(false); 410 } 411 412 return mListMenuPresenter.getMenuView(new FrameLayout(context)); 413 } 414 415 @Override 416 public boolean onBackPressed() { 417 // Back cancels action modes first. 418 if (mActionMode != null) { 419 mActionMode.finish(); 420 return true; 421 } 422 423 // Next collapse any expanded action views. 424 if (mActionBarView != null && mActionBarView.hasExpandedActionView()) { 425 mActionBarView.collapseActionView(); 426 return true; 427 } 428 429 return false; 430 } 431 432 @Override 433 void setSupportProgressBarVisibility(boolean visible) { 434 updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON : 435 Window.PROGRESS_VISIBILITY_OFF); 436 } 437 438 @Override 439 void setSupportProgressBarIndeterminateVisibility(boolean visible) { 440 updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON : 441 Window.PROGRESS_VISIBILITY_OFF); 442 } 443 444 @Override 445 void setSupportProgressBarIndeterminate(boolean indeterminate) { 446 updateProgressBars(indeterminate ? Window.PROGRESS_INDETERMINATE_ON 447 : Window.PROGRESS_INDETERMINATE_OFF); 448 } 449 450 @Override 451 void setSupportProgress(int progress) { 452 updateProgressBars(Window.PROGRESS_START + progress); 453 } 454 455 @Override 456 ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { 457 return new ActionBarDrawableToggleImpl(); 458 } 459 460 /** 461 * Progress Bar function. Mostly extracted from PhoneWindow.java 462 */ 463 private void updateProgressBars(int value) { 464 ProgressBarICS circularProgressBar = getCircularProgressBar(); 465 ProgressBarICS horizontalProgressBar = getHorizontalProgressBar(); 466 467 if (value == Window.PROGRESS_VISIBILITY_ON) { 468 if (mFeatureProgress) { 469 int level = horizontalProgressBar.getProgress(); 470 int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? 471 View.VISIBLE : View.INVISIBLE; 472 horizontalProgressBar.setVisibility(visibility); 473 } 474 if (mFeatureIndeterminateProgress) { 475 circularProgressBar.setVisibility(View.VISIBLE); 476 } 477 } else if (value == Window.PROGRESS_VISIBILITY_OFF) { 478 if (mFeatureProgress) { 479 horizontalProgressBar.setVisibility(View.GONE); 480 } 481 if (mFeatureIndeterminateProgress) { 482 circularProgressBar.setVisibility(View.GONE); 483 } 484 } else if (value == Window.PROGRESS_INDETERMINATE_ON) { 485 horizontalProgressBar.setIndeterminate(true); 486 } else if (value == Window.PROGRESS_INDETERMINATE_OFF) { 487 horizontalProgressBar.setIndeterminate(false); 488 } else if (Window.PROGRESS_START <= value && value <= Window.PROGRESS_END) { 489 // We want to set the progress value before testing for visibility 490 // so that when the progress bar becomes visible again, it has the 491 // correct level. 492 horizontalProgressBar.setProgress(value - Window.PROGRESS_START); 493 494 if (value < Window.PROGRESS_END) { 495 showProgressBars(horizontalProgressBar, circularProgressBar); 496 } else { 497 hideProgressBars(horizontalProgressBar, circularProgressBar); 498 } 499 } 500 } 501 502 private void showProgressBars(ProgressBarICS horizontalProgressBar, 503 ProgressBarICS spinnyProgressBar) { 504 if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.INVISIBLE) { 505 spinnyProgressBar.setVisibility(View.VISIBLE); 506 } 507 // Only show the progress bars if the primary progress is not complete 508 if (mFeatureProgress && horizontalProgressBar.getProgress() < 10000) { 509 horizontalProgressBar.setVisibility(View.VISIBLE); 510 } 511 } 512 513 private void hideProgressBars(ProgressBarICS horizontalProgressBar, 514 ProgressBarICS spinnyProgressBar) { 515 if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.VISIBLE) { 516 spinnyProgressBar.setVisibility(View.INVISIBLE); 517 } 518 if (mFeatureProgress && horizontalProgressBar.getVisibility() == View.VISIBLE) { 519 horizontalProgressBar.setVisibility(View.INVISIBLE); 520 } 521 } 522 523 private ProgressBarICS getCircularProgressBar() { 524 ProgressBarICS pb = (ProgressBarICS) mActionBarView.findViewById(R.id.progress_circular); 525 if (pb != null) { 526 pb.setVisibility(View.INVISIBLE); 527 } 528 return pb; 529 } 530 531 private ProgressBarICS getHorizontalProgressBar() { 532 ProgressBarICS pb = (ProgressBarICS) mActionBarView.findViewById(R.id.progress_horizontal); 533 if (pb != null) { 534 pb.setVisibility(View.INVISIBLE); 535 } 536 return pb; 537 } 538 539 private boolean initializePanelMenu() { 540 mMenu = new MenuBuilder(getActionBarThemedContext()); 541 mMenu.setCallback(this); 542 return true; 543 } 544 545 private boolean preparePanel() { 546 // Already prepared (isPrepared will be reset to false later) 547 if (mPanelIsPrepared) { 548 return true; 549 } 550 551 // Init the panel state's menu--return false if init failed 552 if (mMenu == null || mPanelRefreshContent) { 553 if (mMenu == null) { 554 if (!initializePanelMenu() || (mMenu == null)) { 555 return false; 556 } 557 } 558 559 if (mActionBarView != null) { 560 mActionBarView.setMenu(mMenu, this); 561 } 562 563 // Creating the panel menu will involve a lot of manipulation; 564 // don't dispatch change events to presenters until we're done. 565 mMenu.stopDispatchingItemsChanged(); 566 567 // Call callback, and return if it doesn't want to display menu. 568 if (!mActivity.superOnCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, mMenu)) { 569 // Ditch the menu created above 570 mMenu = null; 571 572 if (mActionBarView != null) { 573 // Don't show it in the action bar either 574 mActionBarView.setMenu(null, this); 575 } 576 577 return false; 578 } 579 580 mPanelRefreshContent = false; 581 } 582 583 // Preparing the panel menu can involve a lot of manipulation; 584 // don't dispatch change events to presenters until we're done. 585 mMenu.stopDispatchingItemsChanged(); 586 587 // Restore action view state before we prepare. This gives apps 588 // an opportunity to override frozen/restored state in onPrepare. 589 if (mPanelFrozenActionViewState != null) { 590 mMenu.restoreActionViewStates(mPanelFrozenActionViewState); 591 mPanelFrozenActionViewState = null; 592 } 593 594 // Callback and return if the callback does not want to show the menu 595 if (!mActivity.superOnPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, mMenu)) { 596 if (mActionBarView != null) { 597 // The app didn't want to show the menu for now but it still exists. 598 // Clear it out of the action bar. 599 mActionBarView.setMenu(null, this); 600 } 601 mMenu.startDispatchingItemsChanged(); 602 return false; 603 } 604 605 mMenu.startDispatchingItemsChanged(); 606 607 // Set other state 608 mPanelIsPrepared = true; 609 610 return true; 611 } 612 613 /** 614 * Clears out internal reference when the action mode is destroyed. 615 */ 616 private class ActionModeCallbackWrapper implements ActionMode.Callback { 617 private ActionMode.Callback mWrapped; 618 619 public ActionModeCallbackWrapper(ActionMode.Callback wrapped) { 620 mWrapped = wrapped; 621 } 622 623 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 624 return mWrapped.onCreateActionMode(mode, menu); 625 } 626 627 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 628 return mWrapped.onPrepareActionMode(mode, menu); 629 } 630 631 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 632 return mWrapped.onActionItemClicked(mode, item); 633 } 634 635 public void onDestroyActionMode(ActionMode mode) { 636 mWrapped.onDestroyActionMode(mode); 637 mActivity.onSupportActionModeFinished(mode); 638 mActionMode = null; 639 } 640 } 641 642 private class ActionBarDrawableToggleImpl 643 implements ActionBarDrawerToggle.Delegate { 644 645 @Override 646 public Drawable getThemeUpIndicator() { 647 final TypedArray a = mActivity.obtainStyledAttributes(ACTION_BAR_DRAWABLE_TOGGLE_ATTRS); 648 final Drawable result = a.getDrawable(0); 649 a.recycle(); 650 return result; 651 } 652 653 @Override 654 public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { 655 if (mActionBarView != null) { 656 mActionBarView.setHomeAsUpIndicator(upDrawable); 657 } 658 } 659 660 @Override 661 public void setActionBarDescription(int contentDescRes) { 662 // No support for setting Action Bar content description 663 } 664 } 665 666} 667