ActionBarImpl.java revision 0c24a5514c1ff143a223720a090b19a86a75945f
1/* 2 * Copyright (C) 2010 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 com.android.internal.app; 18 19import com.android.internal.view.menu.MenuBuilder; 20import com.android.internal.view.menu.MenuPopupHelper; 21import com.android.internal.view.menu.SubMenuBuilder; 22import com.android.internal.widget.ActionBarContextView; 23import com.android.internal.widget.ActionBarView; 24 25import android.app.ActionBar; 26import android.app.Activity; 27import android.app.Dialog; 28import android.app.Fragment; 29import android.app.FragmentTransaction; 30import android.content.Context; 31import android.graphics.drawable.Drawable; 32import android.os.Handler; 33import android.view.ActionMode; 34import android.view.Menu; 35import android.view.MenuInflater; 36import android.view.MenuItem; 37import android.view.View; 38import android.widget.LinearLayout; 39import android.widget.SpinnerAdapter; 40import android.widget.ViewAnimator; 41 42import java.lang.ref.WeakReference; 43import java.util.ArrayList; 44 45/** 46 * ActionBarImpl is the ActionBar implementation used 47 * by devices of all screen sizes. If it detects a compatible decor, 48 * it will split contextual modes across both the ActionBarView at 49 * the top of the screen and a horizontal LinearLayout at the bottom 50 * which is normally hidden. 51 */ 52public class ActionBarImpl extends ActionBar { 53 private static final int NORMAL_VIEW = 0; 54 private static final int CONTEXT_VIEW = 1; 55 56 private Context mContext; 57 private Activity mActivity; 58 private Dialog mDialog; 59 60 private ViewAnimator mAnimatorView; 61 private ActionBarView mActionView; 62 private ActionBarContextView mUpperContextView; 63 private LinearLayout mLowerContextView; 64 65 private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>(); 66 67 private TabImpl mSelectedTab; 68 private int mSavedTabPosition = INVALID_POSITION; 69 70 private ActionMode mActionMode; 71 72 private static final int CONTEXT_DISPLAY_NORMAL = 0; 73 private static final int CONTEXT_DISPLAY_SPLIT = 1; 74 75 private static final int INVALID_POSITION = -1; 76 77 private int mContextDisplayMode; 78 79 private boolean mClosingContext; 80 81 final Handler mHandler = new Handler(); 82 final Runnable mCloseContext = new Runnable() { 83 public void run() { 84 mUpperContextView.closeMode(); 85 if (mLowerContextView != null) { 86 mLowerContextView.removeAllViews(); 87 } 88 mClosingContext = false; 89 } 90 }; 91 92 public ActionBarImpl(Activity activity) { 93 mActivity = activity; 94 init(activity.getWindow().getDecorView()); 95 } 96 97 public ActionBarImpl(Dialog dialog) { 98 mDialog = dialog; 99 init(dialog.getWindow().getDecorView()); 100 } 101 102 private void init(View decor) { 103 mContext = decor.getContext(); 104 mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar); 105 mUpperContextView = (ActionBarContextView) decor.findViewById( 106 com.android.internal.R.id.action_context_bar); 107 mLowerContextView = (LinearLayout) decor.findViewById( 108 com.android.internal.R.id.lower_action_context_bar); 109 mAnimatorView = (ViewAnimator) decor.findViewById( 110 com.android.internal.R.id.action_bar_animator); 111 112 if (mActionView == null || mUpperContextView == null || mAnimatorView == null) { 113 throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + 114 "with a compatible window decor layout"); 115 } 116 117 mActionView.setContextView(mUpperContextView); 118 mContextDisplayMode = mLowerContextView == null ? 119 CONTEXT_DISPLAY_NORMAL : CONTEXT_DISPLAY_SPLIT; 120 } 121 122 @Override 123 public void setTitle(int resId) { 124 setTitle(mContext.getString(resId)); 125 } 126 127 @Override 128 public void setSubtitle(int resId) { 129 setSubtitle(mContext.getString(resId)); 130 } 131 132 public void setCustomNavigationMode(View view) { 133 cleanupTabs(); 134 setCustomView(view); 135 setDisplayOptions(DISPLAY_SHOW_CUSTOM, DISPLAY_SHOW_CUSTOM | DISPLAY_SHOW_TITLE); 136 mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD); 137 mActionView.setCallback(null); 138 } 139 140 public void setDropdownNavigationMode(SpinnerAdapter adapter, NavigationCallback callback) { 141 setDropdownNavigationMode(adapter, callback, -1); 142 } 143 144 public void setDropdownNavigationMode(SpinnerAdapter adapter, NavigationCallback callback, 145 int defaultSelectedPosition) { 146 cleanupTabs(); 147 setDisplayOptions(0, DISPLAY_SHOW_CUSTOM | DISPLAY_SHOW_TITLE); 148 mActionView.setNavigationMode(NAVIGATION_MODE_LIST); 149 setListNavigationCallbacks(adapter, callback); 150 if (defaultSelectedPosition >= 0) { 151 mActionView.setDropdownSelectedPosition(defaultSelectedPosition); 152 } 153 } 154 155 public void setStandardNavigationMode() { 156 cleanupTabs(); 157 setDisplayOptions(DISPLAY_SHOW_TITLE, DISPLAY_SHOW_TITLE | DISPLAY_SHOW_CUSTOM); 158 mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD); 159 mActionView.setCallback(null); 160 } 161 162 public void setSelectedNavigationItem(int position) { 163 switch (mActionView.getNavigationMode()) { 164 case NAVIGATION_MODE_TABS: 165 selectTab(mTabs.get(position)); 166 break; 167 case NAVIGATION_MODE_LIST: 168 mActionView.setDropdownSelectedPosition(position); 169 break; 170 default: 171 throw new IllegalStateException( 172 "setSelectedNavigationIndex not valid for current navigation mode"); 173 } 174 } 175 176 public int getSelectedNavigationItem() { 177 return getSelectedNavigationIndex(); 178 } 179 180 public void removeAllTabs() { 181 cleanupTabs(); 182 } 183 184 private void cleanupTabs() { 185 if (mSelectedTab != null) { 186 selectTab(null); 187 } 188 mTabs.clear(); 189 mActionView.removeAllTabs(); 190 mSavedTabPosition = INVALID_POSITION; 191 } 192 193 public void setTitle(CharSequence title) { 194 mActionView.setTitle(title); 195 } 196 197 public void setSubtitle(CharSequence subtitle) { 198 mActionView.setSubtitle(subtitle); 199 } 200 201 public void setDisplayOptions(int options) { 202 mActionView.setDisplayOptions(options); 203 } 204 205 public void setDisplayOptions(int options, int mask) { 206 final int current = mActionView.getDisplayOptions(); 207 mActionView.setDisplayOptions((options & mask) | (current & ~mask)); 208 } 209 210 public void setBackgroundDrawable(Drawable d) { 211 mActionView.setBackgroundDrawable(d); 212 } 213 214 public View getCustomNavigationView() { 215 return mActionView.getCustomNavigationView(); 216 } 217 218 public CharSequence getTitle() { 219 return mActionView.getTitle(); 220 } 221 222 public CharSequence getSubtitle() { 223 return mActionView.getSubtitle(); 224 } 225 226 public int getNavigationMode() { 227 return mActionView.getNavigationMode(); 228 } 229 230 public int getDisplayOptions() { 231 return mActionView.getDisplayOptions(); 232 } 233 234 public ActionMode startActionMode(ActionMode.Callback callback) { 235 if (mActionMode != null) { 236 mActionMode.finish(); 237 } 238 239 // Don't wait for the close context mode animation to finish. 240 if (mClosingContext) { 241 mAnimatorView.clearAnimation(); 242 mHandler.removeCallbacks(mCloseContext); 243 mCloseContext.run(); 244 } 245 246 ActionMode mode = new ActionModeImpl(callback); 247 if (callback.onCreateActionMode(mode, mode.getMenu())) { 248 mode.invalidate(); 249 mUpperContextView.initForMode(mode); 250 mAnimatorView.setDisplayedChild(CONTEXT_VIEW); 251 if (mLowerContextView != null) { 252 // TODO animate this 253 mLowerContextView.setVisibility(View.VISIBLE); 254 } 255 mActionMode = mode; 256 return mode; 257 } 258 return null; 259 } 260 261 private void configureTab(Tab tab, int position) { 262 final TabImpl tabi = (TabImpl) tab; 263 final ActionBar.TabListener callback = tabi.getCallback(); 264 265 if (callback == null) { 266 throw new IllegalStateException("Action Bar Tab must have a Callback"); 267 } 268 269 tabi.setPosition(position); 270 mTabs.add(position, tabi); 271 272 final int count = mTabs.size(); 273 for (int i = position + 1; i < count; i++) { 274 mTabs.get(i).setPosition(i); 275 } 276 } 277 278 @Override 279 public void addTab(Tab tab) { 280 addTab(tab, mTabs.isEmpty()); 281 } 282 283 @Override 284 public void addTab(Tab tab, int position) { 285 addTab(tab, position, mTabs.isEmpty()); 286 } 287 288 @Override 289 public void addTab(Tab tab, boolean setSelected) { 290 mActionView.addTab(tab, setSelected); 291 configureTab(tab, mTabs.size()); 292 if (setSelected) { 293 selectTab(tab); 294 } 295 } 296 297 @Override 298 public void addTab(Tab tab, int position, boolean setSelected) { 299 mActionView.addTab(tab, position, setSelected); 300 configureTab(tab, position); 301 if (setSelected) { 302 selectTab(tab); 303 } 304 } 305 306 @Override 307 public Tab newTab() { 308 return new TabImpl(); 309 } 310 311 @Override 312 public void removeTab(Tab tab) { 313 removeTabAt(tab.getPosition()); 314 } 315 316 @Override 317 public void removeTabAt(int position) { 318 int selectedTabPosition = mSelectedTab != null 319 ? mSelectedTab.getPosition() : mSavedTabPosition; 320 mActionView.removeTabAt(position); 321 mTabs.remove(position); 322 323 final int newTabCount = mTabs.size(); 324 for (int i = position; i < newTabCount; i++) { 325 mTabs.get(i).setPosition(i); 326 } 327 328 if (selectedTabPosition == position) { 329 selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); 330 } 331 } 332 333 @Override 334 public void setTabNavigationMode() { 335 if (mActivity == null) { 336 throw new IllegalStateException( 337 "Tab navigation mode cannot be used outside of an Activity"); 338 } 339 setDisplayOptions(0, DISPLAY_SHOW_TITLE | DISPLAY_SHOW_CUSTOM); 340 mActionView.setNavigationMode(NAVIGATION_MODE_TABS); 341 } 342 343 @Override 344 public void selectTab(Tab tab) { 345 if (getNavigationMode() != NAVIGATION_MODE_TABS) { 346 mSavedTabPosition = tab != null ? tab.getPosition() : INVALID_POSITION; 347 return; 348 } 349 350 final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction() 351 .disallowAddToBackStack(); 352 353 if (mSelectedTab == tab) { 354 if (mSelectedTab != null) { 355 mSelectedTab.getCallback().onTabReselected(mSelectedTab, trans); 356 } 357 } else { 358 mActionView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION); 359 if (mSelectedTab != null) { 360 mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans); 361 } 362 mSelectedTab = (TabImpl) tab; 363 if (mSelectedTab != null) { 364 mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans); 365 } 366 } 367 368 if (!trans.isEmpty()) { 369 trans.commit(); 370 } 371 } 372 373 @Override 374 public Tab getSelectedTab() { 375 return mSelectedTab; 376 } 377 378 @Override 379 public int getHeight() { 380 return mActionView.getHeight(); 381 } 382 383 @Override 384 public void show() { 385 // TODO animate! 386 mAnimatorView.setVisibility(View.VISIBLE); 387 } 388 389 @Override 390 public void hide() { 391 // TODO animate! 392 mAnimatorView.setVisibility(View.GONE); 393 } 394 395 public boolean isShowing() { 396 return mAnimatorView.getVisibility() == View.VISIBLE; 397 } 398 399 /** 400 * @hide 401 */ 402 public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback { 403 private ActionMode.Callback mCallback; 404 private MenuBuilder mMenu; 405 private WeakReference<View> mCustomView; 406 407 public ActionModeImpl(ActionMode.Callback callback) { 408 mCallback = callback; 409 mMenu = new MenuBuilder(mActionView.getContext()) 410 .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 411 mMenu.setCallback(this); 412 } 413 414 @Override 415 public MenuInflater getMenuInflater() { 416 return new MenuInflater(mContext); 417 } 418 419 @Override 420 public Menu getMenu() { 421 return mMenu; 422 } 423 424 @Override 425 public void finish() { 426 if (mActionMode != this) { 427 // Not the active action mode - no-op 428 return; 429 } 430 431 mCallback.onDestroyActionMode(this); 432 mAnimatorView.setDisplayedChild(NORMAL_VIEW); 433 434 // Clear out the context mode views after the animation finishes 435 mClosingContext = true; 436 mHandler.postDelayed(mCloseContext, mAnimatorView.getOutAnimation().getDuration()); 437 438 if (mLowerContextView != null && mLowerContextView.getVisibility() != View.GONE) { 439 // TODO Animate this 440 mLowerContextView.setVisibility(View.GONE); 441 } 442 mActionMode = null; 443 } 444 445 @Override 446 public void invalidate() { 447 if (mCallback.onPrepareActionMode(this, mMenu)) { 448 // Refresh content in both context views 449 } 450 } 451 452 @Override 453 public void setCustomView(View view) { 454 mUpperContextView.setCustomView(view); 455 mCustomView = new WeakReference<View>(view); 456 } 457 458 @Override 459 public void setSubtitle(CharSequence subtitle) { 460 mUpperContextView.setSubtitle(subtitle); 461 } 462 463 @Override 464 public void setTitle(CharSequence title) { 465 mUpperContextView.setTitle(title); 466 } 467 468 @Override 469 public void setTitle(int resId) { 470 setTitle(mActivity.getString(resId)); 471 } 472 473 @Override 474 public void setSubtitle(int resId) { 475 setSubtitle(mActivity.getString(resId)); 476 } 477 478 @Override 479 public CharSequence getTitle() { 480 return mUpperContextView.getTitle(); 481 } 482 483 @Override 484 public CharSequence getSubtitle() { 485 return mUpperContextView.getSubtitle(); 486 } 487 488 @Override 489 public View getCustomView() { 490 return mCustomView != null ? mCustomView.get() : null; 491 } 492 493 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 494 return mCallback.onActionItemClicked(this, item); 495 } 496 497 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 498 } 499 500 public boolean onSubMenuSelected(SubMenuBuilder subMenu) { 501 if (!subMenu.hasVisibleItems()) { 502 return true; 503 } 504 505 new MenuPopupHelper(mContext, subMenu).show(); 506 return true; 507 } 508 509 public void onCloseSubMenu(SubMenuBuilder menu) { 510 } 511 512 public void onMenuModeChange(MenuBuilder menu) { 513 invalidate(); 514 mUpperContextView.showOverflowMenu(); 515 } 516 } 517 518 /** 519 * @hide 520 */ 521 public class TabImpl extends ActionBar.Tab { 522 private ActionBar.TabListener mCallback; 523 private Object mTag; 524 private Drawable mIcon; 525 private CharSequence mText; 526 private int mPosition; 527 private View mCustomView; 528 529 @Override 530 public Object getTag() { 531 return mTag; 532 } 533 534 @Override 535 public Tab setTag(Object tag) { 536 mTag = tag; 537 return this; 538 } 539 540 public ActionBar.TabListener getCallback() { 541 return mCallback; 542 } 543 544 @Override 545 public Tab setTabListener(ActionBar.TabListener callback) { 546 mCallback = callback; 547 return this; 548 } 549 550 @Override 551 public View getCustomView() { 552 return mCustomView; 553 } 554 555 @Override 556 public Tab setCustomView(View view) { 557 mCustomView = view; 558 return this; 559 } 560 561 @Override 562 public Drawable getIcon() { 563 return mIcon; 564 } 565 566 @Override 567 public int getPosition() { 568 return mPosition; 569 } 570 571 public void setPosition(int position) { 572 mPosition = position; 573 } 574 575 @Override 576 public CharSequence getText() { 577 return mText; 578 } 579 580 @Override 581 public Tab setIcon(Drawable icon) { 582 mIcon = icon; 583 return this; 584 } 585 586 @Override 587 public Tab setText(CharSequence text) { 588 mText = text; 589 return this; 590 } 591 592 @Override 593 public void select() { 594 selectTab(this); 595 } 596 } 597 598 @Override 599 public void setCustomView(View view) { 600 mActionView.setCustomNavigationView(view); 601 } 602 603 @Override 604 public void setCustomView(View view, LayoutParams layoutParams) { 605 view.setLayoutParams(layoutParams); 606 mActionView.setCustomNavigationView(view); 607 } 608 609 @Override 610 public void setListNavigationCallbacks(SpinnerAdapter adapter, NavigationCallback callback) { 611 mActionView.setDropdownAdapter(adapter); 612 mActionView.setCallback(callback); 613 } 614 615 @Override 616 public int getSelectedNavigationIndex() { 617 switch (mActionView.getNavigationMode()) { 618 case NAVIGATION_MODE_TABS: 619 return mSelectedTab.getPosition(); 620 case NAVIGATION_MODE_LIST: 621 return mActionView.getDropdownSelectedPosition(); 622 default: 623 return -1; 624 } 625 } 626 627 @Override 628 public int getNavigationItemCount() { 629 switch (mActionView.getNavigationMode()) { 630 case NAVIGATION_MODE_TABS: 631 return mTabs.size(); 632 case NAVIGATION_MODE_LIST: 633 SpinnerAdapter adapter = mActionView.getDropdownAdapter(); 634 return adapter != null ? adapter.getCount() : 0; 635 default: 636 return 0; 637 } 638 } 639 640 @Override 641 public int getTabCount() { 642 return mTabs.size(); 643 } 644 645 @Override 646 public void setNavigationMode(int mode) { 647 final int oldMode = mActionView.getNavigationMode(); 648 switch (oldMode) { 649 case NAVIGATION_MODE_TABS: 650 mSavedTabPosition = getSelectedNavigationIndex(); 651 selectTab(null); 652 break; 653 } 654 mActionView.setNavigationMode(mode); 655 switch (mode) { 656 case NAVIGATION_MODE_TABS: 657 if (mSavedTabPosition != INVALID_POSITION) { 658 setSelectedNavigationItem(mSavedTabPosition); 659 mSavedTabPosition = INVALID_POSITION; 660 } 661 break; 662 } 663 } 664 665 @Override 666 public Tab getTabAt(int index) { 667 return mTabs.get(index); 668 } 669 670 /** 671 * This fragment is added when we're keeping a back stack in a tab switch 672 * transaction. We use it to change the selected tab in the action bar view 673 * when we back out. 674 */ 675 private class SwitchSelectedTabViewFragment extends Fragment { 676 private int mSelectedTabIndex; 677 678 public SwitchSelectedTabViewFragment(int oldSelectedTab) { 679 mSelectedTabIndex = oldSelectedTab; 680 } 681 682 @Override 683 public void onDetach() { 684 if (mSelectedTabIndex >= 0 && mSelectedTabIndex < getTabCount()) { 685 mActionView.setTabSelected(mSelectedTabIndex); 686 } 687 } 688 } 689} 690