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 17 package android.support.v7.app; 18 19 import android.content.Context; 20 import android.content.res.Configuration; 21 import android.content.res.TypedArray; 22 import android.graphics.drawable.Drawable; 23 import android.support.v4.app.ActionBarDrawerToggle; 24 import android.support.v4.view.WindowCompat; 25 import android.support.v7.appcompat.R; 26 import android.support.v7.internal.view.menu.ListMenuPresenter; 27 import android.support.v7.internal.view.menu.MenuBuilder; 28 import android.support.v7.internal.view.menu.MenuPresenter; 29 import android.support.v7.internal.view.menu.MenuView; 30 import android.support.v7.internal.view.menu.MenuWrapperFactory; 31 import android.support.v7.internal.widget.ActionBarContainer; 32 import android.support.v7.internal.widget.ActionBarContextView; 33 import android.support.v7.internal.widget.ActionBarView; 34 import android.support.v7.internal.widget.ProgressBarICS; 35 import android.support.v7.view.ActionMode; 36 import android.view.Menu; 37 import android.view.MenuItem; 38 import android.view.View; 39 import android.view.ViewGroup; 40 import android.view.Window; 41 import android.widget.FrameLayout; 42 43 class 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 mActivity.onSupportContentChanged(); 130 } 131 132 @Override 133 public void setContentView(int resId) { 134 ensureSubDecor(); 135 if (mHasActionBar) { 136 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 137 contentParent.removeAllViews(); 138 mActivity.getLayoutInflater().inflate(resId, contentParent); 139 } else { 140 mActivity.superSetContentView(resId); 141 } 142 mActivity.onSupportContentChanged(); 143 } 144 145 @Override 146 public void setContentView(View v, ViewGroup.LayoutParams lp) { 147 ensureSubDecor(); 148 if (mHasActionBar) { 149 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 150 contentParent.removeAllViews(); 151 contentParent.addView(v, lp); 152 } else { 153 mActivity.superSetContentView(v, lp); 154 } 155 mActivity.onSupportContentChanged(); 156 } 157 158 @Override 159 public void addContentView(View v, ViewGroup.LayoutParams lp) { 160 ensureSubDecor(); 161 if (mHasActionBar) { 162 ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content); 163 contentParent.addView(v, lp); 164 } else { 165 mActivity.superSetContentView(v, lp); 166 } 167 mActivity.onSupportContentChanged(); 168 } 169 170 @Override 171 public void onContentChanged() { 172 // Ignore all calls to this method as we call onSupportContentChanged manually above 173 } 174 175 final void ensureSubDecor() { 176 if (mHasActionBar && !mSubDecorInstalled) { 177 if (mOverlayActionBar) { 178 mActivity.superSetContentView(R.layout.abc_action_bar_decor_overlay); 179 } else { 180 mActivity.superSetContentView(R.layout.abc_action_bar_decor); 181 } 182 mActionBarView = (ActionBarView) mActivity.findViewById(R.id.action_bar); 183 mActionBarView.setWindowCallback(mActivity); 184 185 /** 186 * Progress Bars 187 */ 188 if (mFeatureProgress) { 189 mActionBarView.initProgress(); 190 } 191 if (mFeatureIndeterminateProgress) { 192 mActionBarView.initIndeterminateProgress(); 193 } 194 195 /** 196 * Split Action Bar 197 */ 198 boolean splitWhenNarrow = UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW 199 .equals(getUiOptionsFromMetadata()); 200 boolean splitActionBar; 201 202 if (splitWhenNarrow) { 203 splitActionBar = mActivity.getResources() 204 .getBoolean(R.bool.abc_split_action_bar_is_narrow); 205 } else { 206 TypedArray a = mActivity.obtainStyledAttributes(R.styleable.ActionBarWindow); 207 splitActionBar = a 208 .getBoolean(R.styleable.ActionBarWindow_windowSplitActionBar, false); 209 a.recycle(); 210 } 211 212 final ActionBarContainer splitView = (ActionBarContainer) mActivity.findViewById( 213 R.id.split_action_bar); 214 if (splitView != null) { 215 mActionBarView.setSplitView(splitView); 216 mActionBarView.setSplitActionBar(splitActionBar); 217 mActionBarView.setSplitWhenNarrow(splitWhenNarrow); 218 219 final ActionBarContextView cab = (ActionBarContextView) mActivity.findViewById( 220 R.id.action_context_bar); 221 cab.setSplitView(splitView); 222 cab.setSplitActionBar(splitActionBar); 223 cab.setSplitWhenNarrow(splitWhenNarrow); 224 } 225 226 // Change our content FrameLayout to use the android.R.id.content id. 227 // Useful for fragments. 228 View content = mActivity.findViewById(android.R.id.content); 229 content.setId(View.NO_ID); 230 View abcContent = mActivity.findViewById(R.id.action_bar_activity_content); 231 abcContent.setId(android.R.id.content); 232 233 // A title was set before we've install the decor so set it now. 234 if (mTitleToSet != null) { 235 mActionBarView.setWindowTitle(mTitleToSet); 236 mTitleToSet = null; 237 } 238 239 mSubDecorInstalled = true; 240 supportInvalidateOptionsMenu(); 241 } 242 } 243 244 @Override 245 public boolean supportRequestWindowFeature(int featureId) { 246 switch (featureId) { 247 case WindowCompat.FEATURE_ACTION_BAR: 248 mHasActionBar = true; 249 return true; 250 case WindowCompat.FEATURE_ACTION_BAR_OVERLAY: 251 mOverlayActionBar = true; 252 return true; 253 case Window.FEATURE_PROGRESS: 254 mFeatureProgress = true; 255 return true; 256 case Window.FEATURE_INDETERMINATE_PROGRESS: 257 mFeatureIndeterminateProgress = true; 258 return true; 259 default: 260 return mActivity.requestWindowFeature(featureId); 261 } 262 } 263 264 @Override 265 public void onTitleChanged(CharSequence title) { 266 if (mActionBarView != null) { 267 mActionBarView.setWindowTitle(title); 268 } else { 269 mTitleToSet = title; 270 } 271 } 272 273 @Override 274 public View onCreatePanelView(int featureId) { 275 View createdPanelView = null; 276 277 if (featureId == Window.FEATURE_OPTIONS_PANEL) { 278 boolean show = true; 279 MenuBuilder menu = mMenu; 280 281 if (mActionMode == null) { 282 // We only want to dispatch Activity/Fragment menu calls if there isn't 283 // currently an action mode 284 285 if (menu == null) { 286 // We don't have a menu created, so create one 287 menu = createMenu(); 288 setMenu(menu); 289 290 // Make sure we're not dispatching item changes to presenters 291 menu.stopDispatchingItemsChanged(); 292 // Dispatch onCreateOptionsMenu 293 show = mActivity.superOnCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu); 294 } 295 296 if (show) { 297 // Make sure we're not dispatching item changes to presenters 298 menu.stopDispatchingItemsChanged(); 299 // Dispatch onPrepareOptionsMenu 300 show = mActivity.superOnPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu); 301 } 302 } 303 304 if (show) { 305 createdPanelView = (View) getListMenuView(mActivity, this); 306 307 // Allow menu to start dispatching changes to presenters 308 menu.startDispatchingItemsChanged(); 309 } else { 310 // If the menu isn't being shown, we no longer need it 311 setMenu(null); 312 } 313 } 314 315 return createdPanelView; 316 } 317 318 @Override 319 public boolean onCreatePanelMenu(int featureId, Menu menu) { 320 if (featureId != Window.FEATURE_OPTIONS_PANEL) { 321 return mActivity.superOnCreatePanelMenu(featureId, menu); 322 } 323 return false; 324 } 325 326 @Override 327 public boolean onPreparePanel(int featureId, View view, Menu menu) { 328 if (featureId != Window.FEATURE_OPTIONS_PANEL) { 329 return mActivity.superOnPreparePanel(featureId, view, menu); 330 } 331 return false; 332 } 333 334 @Override 335 public boolean onMenuItemSelected(int featureId, MenuItem item) { 336 if (featureId == Window.FEATURE_OPTIONS_PANEL) { 337 item = MenuWrapperFactory.createMenuItemWrapper(item); 338 } 339 return mActivity.superOnMenuItemSelected(featureId, item); 340 } 341 342 @Override 343 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 344 return mActivity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); 345 } 346 347 @Override 348 public void onMenuModeChange(MenuBuilder menu) { 349 reopenMenu(menu, true); 350 } 351 352 @Override 353 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 354 mActivity.closeOptionsMenu(); 355 } 356 357 @Override 358 public boolean onOpenSubMenu(MenuBuilder subMenu) { 359 return false; 360 } 361 362 @Override 363 public ActionMode startSupportActionMode(ActionMode.Callback callback) { 364 if (callback == null) { 365 throw new IllegalArgumentException("ActionMode callback can not be null."); 366 } 367 368 if (mActionMode != null) { 369 mActionMode.finish(); 370 } 371 372 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback); 373 374 ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar(); 375 if (ab != null) { 376 mActionMode = ab.startActionMode(wrappedCallback); 377 } 378 379 if (mActionMode != null) { 380 mActivity.onSupportActionModeStarted(mActionMode); 381 } 382 return mActionMode; 383 } 384 385 @Override 386 public void supportInvalidateOptionsMenu() { 387 if (!mInvalidateMenuPosted) { 388 mInvalidateMenuPosted = true; 389 mActivity.getWindow().getDecorView().post(mInvalidateMenuRunnable); 390 } 391 } 392 393 private MenuBuilder createMenu() { 394 MenuBuilder menu = new MenuBuilder(getActionBarThemedContext()); 395 menu.setCallback(this); 396 return menu; 397 } 398 399 private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) { 400 if (mActionBarView != null && mActionBarView.isOverflowReserved()) { 401 if (!mActionBarView.isOverflowMenuShowing() || !toggleMenuMode) { 402 if (mActionBarView.getVisibility() == View.VISIBLE) { 403 mActionBarView.showOverflowMenu(); 404 } 405 } else { 406 mActionBarView.hideOverflowMenu(); 407 } 408 return; 409 } 410 411 menu.close(); 412 } 413 414 private MenuView getListMenuView(Context context, MenuPresenter.Callback cb) { 415 if (mMenu == null) { 416 return null; 417 } 418 419 if (mListMenuPresenter == null) { 420 TypedArray a = context.obtainStyledAttributes(R.styleable.Theme); 421 final int listPresenterTheme = a.getResourceId( 422 R.styleable.Theme_panelMenuListTheme, 423 R.style.Theme_AppCompat_CompactMenu); 424 a.recycle(); 425 426 mListMenuPresenter = new ListMenuPresenter( 427 R.layout.abc_list_menu_item_layout, listPresenterTheme); 428 mListMenuPresenter.setCallback(cb); 429 mMenu.addMenuPresenter(mListMenuPresenter); 430 } else { 431 // Make sure we update the ListView 432 mListMenuPresenter.updateMenuView(false); 433 } 434 435 return mListMenuPresenter.getMenuView(new FrameLayout(context)); 436 } 437 438 private void setMenu(MenuBuilder menu) { 439 if (menu == mMenu) { 440 return; 441 } 442 443 if (mMenu != null) { 444 mMenu.removeMenuPresenter(mListMenuPresenter); 445 } 446 mMenu = menu; 447 448 if (menu != null && mListMenuPresenter != null) { 449 // Only update list menu if there isn't an action mode menu 450 menu.addMenuPresenter(mListMenuPresenter); 451 } 452 if (mActionBarView != null) { 453 mActionBarView.setMenu(menu, this); 454 } 455 } 456 457 @Override 458 public boolean onBackPressed() { 459 // Back cancels action modes first. 460 if (mActionMode != null) { 461 mActionMode.finish(); 462 return true; 463 } 464 465 // Next collapse any expanded action views. 466 if (mActionBarView != null && mActionBarView.hasExpandedActionView()) { 467 mActionBarView.collapseActionView(); 468 return true; 469 } 470 471 return false; 472 } 473 474 @Override 475 void setSupportProgressBarVisibility(boolean visible) { 476 updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON : 477 Window.PROGRESS_VISIBILITY_OFF); 478 } 479 480 @Override 481 void setSupportProgressBarIndeterminateVisibility(boolean visible) { 482 updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON : 483 Window.PROGRESS_VISIBILITY_OFF); 484 } 485 486 @Override 487 void setSupportProgressBarIndeterminate(boolean indeterminate) { 488 updateProgressBars(indeterminate ? Window.PROGRESS_INDETERMINATE_ON 489 : Window.PROGRESS_INDETERMINATE_OFF); 490 } 491 492 @Override 493 void setSupportProgress(int progress) { 494 updateProgressBars(Window.PROGRESS_START + progress); 495 } 496 497 @Override 498 ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { 499 return new ActionBarDrawableToggleImpl(); 500 } 501 502 /** 503 * Progress Bar function. Mostly extracted from PhoneWindow.java 504 */ 505 private void updateProgressBars(int value) { 506 ProgressBarICS circularProgressBar = getCircularProgressBar(); 507 ProgressBarICS horizontalProgressBar = getHorizontalProgressBar(); 508 509 if (value == Window.PROGRESS_VISIBILITY_ON) { 510 if (mFeatureProgress) { 511 int level = horizontalProgressBar.getProgress(); 512 int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ? 513 View.VISIBLE : View.INVISIBLE; 514 horizontalProgressBar.setVisibility(visibility); 515 } 516 if (mFeatureIndeterminateProgress) { 517 circularProgressBar.setVisibility(View.VISIBLE); 518 } 519 } else if (value == Window.PROGRESS_VISIBILITY_OFF) { 520 if (mFeatureProgress) { 521 horizontalProgressBar.setVisibility(View.GONE); 522 } 523 if (mFeatureIndeterminateProgress) { 524 circularProgressBar.setVisibility(View.GONE); 525 } 526 } else if (value == Window.PROGRESS_INDETERMINATE_ON) { 527 horizontalProgressBar.setIndeterminate(true); 528 } else if (value == Window.PROGRESS_INDETERMINATE_OFF) { 529 horizontalProgressBar.setIndeterminate(false); 530 } else if (Window.PROGRESS_START <= value && value <= Window.PROGRESS_END) { 531 // We want to set the progress value before testing for visibility 532 // so that when the progress bar becomes visible again, it has the 533 // correct level. 534 horizontalProgressBar.setProgress(value - Window.PROGRESS_START); 535 536 if (value < Window.PROGRESS_END) { 537 showProgressBars(horizontalProgressBar, circularProgressBar); 538 } else { 539 hideProgressBars(horizontalProgressBar, circularProgressBar); 540 } 541 } 542 } 543 544 private void showProgressBars(ProgressBarICS horizontalProgressBar, 545 ProgressBarICS spinnyProgressBar) { 546 if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.INVISIBLE) { 547 spinnyProgressBar.setVisibility(View.VISIBLE); 548 } 549 // Only show the progress bars if the primary progress is not complete 550 if (mFeatureProgress && horizontalProgressBar.getProgress() < 10000) { 551 horizontalProgressBar.setVisibility(View.VISIBLE); 552 } 553 } 554 555 private void hideProgressBars(ProgressBarICS horizontalProgressBar, 556 ProgressBarICS spinnyProgressBar) { 557 if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.VISIBLE) { 558 spinnyProgressBar.setVisibility(View.INVISIBLE); 559 } 560 if (mFeatureProgress && horizontalProgressBar.getVisibility() == View.VISIBLE) { 561 horizontalProgressBar.setVisibility(View.INVISIBLE); 562 } 563 } 564 565 private ProgressBarICS getCircularProgressBar() { 566 ProgressBarICS pb = (ProgressBarICS) mActionBarView.findViewById(R.id.progress_circular); 567 if (pb != null) { 568 pb.setVisibility(View.INVISIBLE); 569 } 570 return pb; 571 } 572 573 private ProgressBarICS getHorizontalProgressBar() { 574 ProgressBarICS pb = (ProgressBarICS) mActionBarView.findViewById(R.id.progress_horizontal); 575 if (pb != null) { 576 pb.setVisibility(View.INVISIBLE); 577 } 578 return pb; 579 } 580 581 /** 582 * Clears out internal reference when the action mode is destroyed. 583 */ 584 private class ActionModeCallbackWrapper implements ActionMode.Callback { 585 private ActionMode.Callback mWrapped; 586 587 public ActionModeCallbackWrapper(ActionMode.Callback wrapped) { 588 mWrapped = wrapped; 589 } 590 591 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 592 return mWrapped.onCreateActionMode(mode, menu); 593 } 594 595 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 596 return mWrapped.onPrepareActionMode(mode, menu); 597 } 598 599 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 600 return mWrapped.onActionItemClicked(mode, item); 601 } 602 603 public void onDestroyActionMode(ActionMode mode) { 604 mWrapped.onDestroyActionMode(mode); 605 mActivity.onSupportActionModeFinished(mode); 606 mActionMode = null; 607 } 608 } 609 610 private class ActionBarDrawableToggleImpl 611 implements ActionBarDrawerToggle.Delegate { 612 613 @Override 614 public Drawable getThemeUpIndicator() { 615 final TypedArray a = mActivity.obtainStyledAttributes(ACTION_BAR_DRAWABLE_TOGGLE_ATTRS); 616 final Drawable result = a.getDrawable(0); 617 a.recycle(); 618 return result; 619 } 620 621 @Override 622 public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { 623 if (mActionBarView != null) { 624 mActionBarView.setHomeAsUpIndicator(upDrawable); 625 } 626 } 627 628 @Override 629 public void setActionBarDescription(int contentDescRes) { 630 // No support for setting Action Bar content description 631 } 632 } 633 634 } 635