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