ActionBarImpl.java revision 0458796f1401732b38660794148f4c5e5602f432
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 != null ? mSelectedTab.getPosition() : -1;
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