ActionBarActivityDelegateBase.java revision 8befe35284f720362ec0a862ec840bd9fb598fe4
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.support.v4.app.ActionBarDrawerToggle; 24import android.support.v4.view.WindowCompat; 25import android.support.v7.appcompat.R; 26import android.support.v7.internal.view.menu.ListMenuPresenter; 27import android.support.v7.internal.view.menu.MenuBuilder; 28import android.support.v7.internal.view.menu.MenuPresenter; 29import android.support.v7.internal.view.menu.MenuView; 30import android.support.v7.internal.view.menu.MenuWrapperFactory; 31import android.support.v7.internal.widget.ActionBarContainer; 32import android.support.v7.internal.widget.ActionBarContextView; 33import android.support.v7.internal.widget.ActionBarView; 34import android.support.v7.internal.widget.ProgressBarICS; 35import android.support.v7.view.ActionMode; 36import android.view.Menu; 37import android.view.MenuItem; 38import android.view.View; 39import android.view.ViewGroup; 40import android.view.Window; 41import android.widget.FrameLayout; 42 43class ActionBarActivityDelegateBase extends ActionBarActivityDelegate implements 44 MenuPresenter.Callback, MenuBuilder.Callback { 45 private static final String TAG = "ActionBarActivityDelegateBase"; 46 47 private static final int[] ACTION_BAR_DRAWABLE_TOGGLE_ATTRS = new int[] { 48 R.attr.homeAsUpIndicator 49 }; 50 51 private ActionBarView mActionBarView; 52 private ListMenuPresenter mListMenuPresenter; 53 private MenuBuilder mMenu; 54 55 private ActionMode mActionMode; 56 57 // true if we have installed a window sub-decor layout. 58 private boolean mSubDecorInstalled; 59 60 private CharSequence mTitleToSet; 61 62 // Used to keep track of Progress Bar Window features 63 private boolean mFeatureProgress, mFeatureIndeterminateProgress; 64 65 private boolean mInvalidateMenuPosted; 66 private final Runnable mInvalidateMenuRunnable = new Runnable() { 67 @Override 68 public void run() { 69 final MenuBuilder menu = createMenu(); 70 if (mActivity.superOnCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) && 71 mActivity.superOnPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) { 72 setMenu(menu); 73 } else { 74 setMenu(null); 75 } 76 77 mInvalidateMenuPosted = false; 78 } 79 }; 80 81 ActionBarActivityDelegateBase(ActionBarActivity activity) { 82 super(activity); 83 } 84 85 @Override 86 public ActionBar createSupportActionBar() { 87 ensureSubDecor(); 88 return new ActionBarImplBase(mActivity, mActivity); 89 } 90 91 @Override 92 public void onConfigurationChanged(Configuration newConfig) { 93 // If this is called before sub-decor is installed, ActionBar will not 94 // be properly initialized. 95 if (mHasActionBar && mSubDecorInstalled) { 96 // Note: The action bar will need to access 97 // view changes from superclass. 98 ActionBarImplBase actionBar = (ActionBarImplBase) getSupportActionBar(); 99 actionBar.onConfigurationChanged(newConfig); 100 } 101 } 102 103 @Override 104 public void onStop() { 105 ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar(); 106 if (ab != null) { 107 ab.setShowHideAnimationEnabled(false); 108 } 109 } 110 111 @Override 112 public void onPostResume() { 113 ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar(); 114 if (ab != null) { 115 ab.setShowHideAnimationEnabled(true); 116 } 117 } 118 119 @Override 120 public void setContentView(View v) { 121 ensureSubDecor(); 122 if (mHasActionBar) { 123 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 124 contentParent.removeAllViews(); 125 contentParent.addView(v); 126 } else { 127 mActivity.superSetContentView(v); 128 } 129 } 130 131 @Override 132 public void setContentView(int resId) { 133 ensureSubDecor(); 134 if (mHasActionBar) { 135 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 136 contentParent.removeAllViews(); 137 mActivity.getLayoutInflater().inflate(resId, contentParent); 138 } else { 139 mActivity.superSetContentView(resId); 140 } 141 } 142 143 @Override 144 public void setContentView(View v, ViewGroup.LayoutParams lp) { 145 ensureSubDecor(); 146 if (mHasActionBar) { 147 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 148 contentParent.removeAllViews(); 149 contentParent.addView(v, lp); 150 } else { 151 mActivity.superSetContentView(v, lp); 152 } 153 } 154 155 @Override 156 public void addContentView(View v, ViewGroup.LayoutParams lp) { 157 ensureSubDecor(); 158 if (mHasActionBar) { 159 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 160 contentParent.addView(v, lp); 161 } else { 162 mActivity.superSetContentView(v, lp); 163 } 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 supportInvalidateOptionsMenu(); 232 } 233 } 234 235 @Override 236 public boolean supportRequestWindowFeature(int featureId) { 237 switch (featureId) { 238 case WindowCompat.FEATURE_ACTION_BAR: 239 mHasActionBar = true; 240 return true; 241 case WindowCompat.FEATURE_ACTION_BAR_OVERLAY: 242 mOverlayActionBar = true; 243 return true; 244 case Window.FEATURE_PROGRESS: 245 mFeatureProgress = true; 246 return true; 247 case Window.FEATURE_INDETERMINATE_PROGRESS: 248 mFeatureIndeterminateProgress = true; 249 return true; 250 default: 251 return mActivity.requestWindowFeature(featureId); 252 } 253 } 254 255 @Override 256 public void onTitleChanged(CharSequence title) { 257 if (mActionBarView != null) { 258 mActionBarView.setWindowTitle(title); 259 } else { 260 mTitleToSet = title; 261 } 262 } 263 264 @Override 265 public View onCreatePanelView(int featureId) { 266 View createdPanelView = null; 267 268 if (featureId == Window.FEATURE_OPTIONS_PANEL) { 269 boolean show = true; 270 MenuBuilder menu = mMenu; 271 272 if (mActionMode == null) { 273 // We only want to dispatch Activity/Fragment menu calls if there isn't 274 // currently an action mode 275 276 if (menu == null) { 277 // We don't have a menu created, so create one 278 menu = createMenu(); 279 setMenu(menu); 280 281 // Make sure we're not dispatching item changes to presenters 282 menu.stopDispatchingItemsChanged(); 283 // Dispatch onCreateOptionsMenu 284 show = mActivity.superOnCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu); 285 } 286 287 if (show) { 288 // Make sure we're not dispatching item changes to presenters 289 menu.stopDispatchingItemsChanged(); 290 // Dispatch onPrepareOptionsMenu 291 show = mActivity.superOnPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu); 292 } 293 } 294 295 if (show) { 296 createdPanelView = (View) getListMenuView(mActivity, this); 297 298 // Allow menu to start dispatching changes to presenters 299 menu.startDispatchingItemsChanged(); 300 } else { 301 // If the menu isn't being shown, we no longer need it 302 setMenu(null); 303 } 304 } 305 306 return createdPanelView; 307 } 308 309 @Override 310 public boolean onCreatePanelMenu(int featureId, Menu menu) { 311 if (featureId != Window.FEATURE_OPTIONS_PANEL) { 312 return mActivity.superOnCreatePanelMenu(featureId, menu); 313 } 314 return false; 315 } 316 317 @Override 318 public boolean onPreparePanel(int featureId, View view, Menu menu) { 319 if (featureId != Window.FEATURE_OPTIONS_PANEL) { 320 return mActivity.superOnPreparePanel(featureId, view, menu); 321 } 322 return false; 323 } 324 325 @Override 326 public boolean onMenuItemSelected(int featureId, MenuItem item) { 327 if (featureId == Window.FEATURE_OPTIONS_PANEL) { 328 item = MenuWrapperFactory.createMenuItemWrapper(item); 329 } 330 return mActivity.superOnMenuItemSelected(featureId, item); 331 } 332 333 @Override 334 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 335 return mActivity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); 336 } 337 338 @Override 339 public void onMenuModeChange(MenuBuilder menu) { 340 reopenMenu(menu, true); 341 } 342 343 @Override 344 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 345 mActivity.closeOptionsMenu(); 346 } 347 348 @Override 349 public boolean onOpenSubMenu(MenuBuilder subMenu) { 350 return false; 351 } 352 353 @Override 354 public ActionMode startSupportActionMode(ActionMode.Callback callback) { 355 if (callback == null) { 356 throw new IllegalArgumentException("ActionMode callback can not be null."); 357 } 358 359 if (mActionMode != null) { 360 mActionMode.finish(); 361 } 362 363 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); 364 365 ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar(); 366 if (ab != null) { 367 mActionMode = ab.startActionMode(wrappedCallback); 368 } 369 370 if (mActionMode != null) { 371 mActivity.onSupportActionModeStarted(mActionMode); 372 } 373 return mActionMode; 374 } 375 376 @Override 377 public void supportInvalidateOptionsMenu() { 378 if (!mInvalidateMenuPosted) { 379 mInvalidateMenuPosted = true; 380 mActivity.getWindow().getDecorView().post(mInvalidateMenuRunnable); 381 } 382 } 383 384 private MenuBuilder createMenu() { 385 MenuBuilder menu = new MenuBuilder(getActionBarThemedContext()); 386 menu.setCallback(this); 387 return menu; 388 } 389 390 private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) { 391 if (mActionBarView != null && mActionBarView.isOverflowReserved()) { 392 if (!mActionBarView.isOverflowMenuShowing() || !toggleMenuMode) { 393 if (mActionBarView.getVisibility() == View.VISIBLE) { 394 mActionBarView.showOverflowMenu(); 395 } 396 } else { 397 mActionBarView.hideOverflowMenu(); 398 } 399 return; 400 } 401 402 menu.close(); 403 } 404 405 private MenuView getListMenuView(Context context, MenuPresenter.Callback cb) { 406 if (mMenu == null) { 407 return null; 408 } 409 410 if (mListMenuPresenter == null) { 411 TypedArray a = context.obtainStyledAttributes(R.styleable.Theme); 412 final int listPresenterTheme = a.getResourceId( 413 R.styleable.Theme_panelMenuListTheme, 414 R.style.Theme_AppCompat_CompactMenu); 415 a.recycle(); 416 417 mListMenuPresenter = new ListMenuPresenter( 418 R.layout.abc_list_menu_item_layout, listPresenterTheme); 419 mListMenuPresenter.setCallback(cb); 420 mMenu.addMenuPresenter(mListMenuPresenter); 421 } else { 422 // Make sure we update the ListView 423 mListMenuPresenter.updateMenuView(false); 424 } 425 426 return mListMenuPresenter.getMenuView(new FrameLayout(context)); 427 } 428 429 private void setMenu(MenuBuilder menu) { 430 if (menu == mMenu) { 431 return; 432 } 433 434 if (mMenu != null) { 435 mMenu.removeMenuPresenter(mListMenuPresenter); 436 } 437 mMenu = menu; 438 439 if (menu != null && mListMenuPresenter != null) { 440 // Only update list menu if there isn't an action mode menu 441 menu.addMenuPresenter(mListMenuPresenter); 442 } 443 if (mActionBarView != null) { 444 mActionBarView.setMenu(menu, this); 445 } 446 } 447 448 @Override 449 public boolean onBackPressed() { 450 // Back cancels action modes first. 451 if (mActionMode != null) { 452 mActionMode.finish(); 453 return true; 454 } 455 456 // Next collapse any expanded action views. 457 if (mActionBarView != null && mActionBarView.hasExpandedActionView()) { 458 mActionBarView.collapseActionView(); 459 return true; 460 } 461 462 return false; 463 } 464 465 @Override 466 void setSupportProgressBarVisibility(boolean visible) { 467 updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON : 468 Window.PROGRESS_VISIBILITY_OFF); 469 } 470 471 @Override 472 void setSupportProgressBarIndeterminateVisibility(boolean visible) { 473 updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON : 474 Window.PROGRESS_VISIBILITY_OFF); 475 } 476 477 @Override 478 void setSupportProgressBarIndeterminate(boolean indeterminate) { 479 updateProgressBars(indeterminate ? Window.PROGRESS_INDETERMINATE_ON 480 : Window.PROGRESS_INDETERMINATE_OFF); 481 } 482 483 @Override 484 void setSupportProgress(int progress) { 485 updateProgressBars(Window.PROGRESS_START + progress); 486 } 487 488 @Override 489 ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { 490 return new ActionBarDrawableToggleImpl(); 491 } 492 493 /** 494 * Progress Bar function. Mostly extracted from PhoneWindow.java 495 */ 496 private void updateProgressBars(int value) { 497 ProgressBarICS circularProgressBar = getCircularProgressBar(); 498 ProgressBarICS horizontalProgressBar = getHorizontalProgressBar(); 499 500 if (value == Window.PROGRESS_VISIBILITY_ON) { 501 if (mFeatureProgress) { 502 int level = horizontalProgressBar.getProgress(); 503 int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? 504 View.VISIBLE : View.INVISIBLE; 505 horizontalProgressBar.setVisibility(visibility); 506 } 507 if (mFeatureIndeterminateProgress) { 508 circularProgressBar.setVisibility(View.VISIBLE); 509 } 510 } else if (value == Window.PROGRESS_VISIBILITY_OFF) { 511 if (mFeatureProgress) { 512 horizontalProgressBar.setVisibility(View.GONE); 513 } 514 if (mFeatureIndeterminateProgress) { 515 circularProgressBar.setVisibility(View.GONE); 516 } 517 } else if (value == Window.PROGRESS_INDETERMINATE_ON) { 518 horizontalProgressBar.setIndeterminate(true); 519 } else if (value == Window.PROGRESS_INDETERMINATE_OFF) { 520 horizontalProgressBar.setIndeterminate(false); 521 } else if (Window.PROGRESS_START <= value && value <= Window.PROGRESS_END) { 522 // We want to set the progress value before testing for visibility 523 // so that when the progress bar becomes visible again, it has the 524 // correct level. 525 horizontalProgressBar.setProgress(value - Window.PROGRESS_START); 526 527 if (value < Window.PROGRESS_END) { 528 showProgressBars(horizontalProgressBar, circularProgressBar); 529 } else { 530 hideProgressBars(horizontalProgressBar, circularProgressBar); 531 } 532 } 533 } 534 535 private void showProgressBars(ProgressBarICS horizontalProgressBar, 536 ProgressBarICS spinnyProgressBar) { 537 if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.INVISIBLE) { 538 spinnyProgressBar.setVisibility(View.VISIBLE); 539 } 540 // Only show the progress bars if the primary progress is not complete 541 if (mFeatureProgress && horizontalProgressBar.getProgress() < 10000) { 542 horizontalProgressBar.setVisibility(View.VISIBLE); 543 } 544 } 545 546 private void hideProgressBars(ProgressBarICS horizontalProgressBar, 547 ProgressBarICS spinnyProgressBar) { 548 if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.VISIBLE) { 549 spinnyProgressBar.setVisibility(View.INVISIBLE); 550 } 551 if (mFeatureProgress && horizontalProgressBar.getVisibility() == View.VISIBLE) { 552 horizontalProgressBar.setVisibility(View.INVISIBLE); 553 } 554 } 555 556 private ProgressBarICS getCircularProgressBar() { 557 ProgressBarICS pb = (ProgressBarICS) mActionBarView.findViewById(R.id.progress_circular); 558 if (pb != null) { 559 pb.setVisibility(View.INVISIBLE); 560 } 561 return pb; 562 } 563 564 private ProgressBarICS getHorizontalProgressBar() { 565 ProgressBarICS pb = (ProgressBarICS) mActionBarView.findViewById(R.id.progress_horizontal); 566 if (pb != null) { 567 pb.setVisibility(View.INVISIBLE); 568 } 569 return pb; 570 } 571 572 /** 573 * Clears out internal reference when the action mode is destroyed. 574 */ 575 private class ActionModeCallbackWrapper implements ActionMode.Callback { 576 private ActionMode.Callback mWrapped; 577 578 public ActionModeCallbackWrapper(ActionMode.Callback wrapped) { 579 mWrapped = wrapped; 580 } 581 582 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 583 return mWrapped.onCreateActionMode(mode, menu); 584 } 585 586 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 587 return mWrapped.onPrepareActionMode(mode, menu); 588 } 589 590 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 591 return mWrapped.onActionItemClicked(mode, item); 592 } 593 594 public void onDestroyActionMode(ActionMode mode) { 595 mWrapped.onDestroyActionMode(mode); 596 mActivity.onSupportActionModeFinished(mode); 597 mActionMode = null; 598 } 599 } 600 601 private class ActionBarDrawableToggleImpl 602 implements ActionBarDrawerToggle.Delegate { 603 604 @Override 605 public Drawable getThemeUpIndicator() { 606 final TypedArray a = mActivity.obtainStyledAttributes(ACTION_BAR_DRAWABLE_TOGGLE_ATTRS); 607 final Drawable result = a.getDrawable(0); 608 a.recycle(); 609 return result; 610 } 611 612 @Override 613 public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { 614 if (mActionBarView != null) { 615 mActionBarView.setHomeAsUpIndicator(upDrawable); 616 } 617 } 618 619 @Override 620 public void setActionBarDescription(int contentDescRes) { 621 // No support for setting Action Bar content description 622 } 623 } 624 625} 626