ActionBarImpl.java revision 5d27977f9da482627ceb19317a2cd70467aff046
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.Fragment;
28import android.app.FragmentTransaction;
29import android.graphics.drawable.Drawable;
30import android.os.Handler;
31import android.view.ActionMode;
32import android.view.Menu;
33import android.view.MenuItem;
34import android.view.View;
35import android.widget.LinearLayout;
36import android.widget.SpinnerAdapter;
37import android.widget.ViewAnimator;
38
39import java.lang.ref.WeakReference;
40import java.util.ArrayList;
41
42/**
43 * ActionBarImpl is the ActionBar implementation used
44 * by devices of all screen sizes. If it detects a compatible decor,
45 * it will split contextual modes across both the ActionBarView at
46 * the top of the screen and a horizontal LinearLayout at the bottom
47 * which is normally hidden.
48 */
49public class ActionBarImpl extends ActionBar {
50    private static final int NORMAL_VIEW = 0;
51    private static final int CONTEXT_VIEW = 1;
52
53    private static final int TAB_SWITCH_SHOW_HIDE = 0;
54    private static final int TAB_SWITCH_ADD_REMOVE = 1;
55
56    private Activity mActivity;
57
58    private ViewAnimator mAnimatorView;
59    private ActionBarView mActionView;
60    private ActionBarContextView mUpperContextView;
61    private LinearLayout mLowerContextView;
62
63    private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
64
65    private int mTabContainerViewId = android.R.id.content;
66    private TabImpl mSelectedTab;
67    private int mTabSwitchMode = TAB_SWITCH_ADD_REMOVE;
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        final View decor = activity.getWindow().getDecorView();
91        mActivity = activity;
92        mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
93        mUpperContextView = (ActionBarContextView) decor.findViewById(
94                com.android.internal.R.id.action_context_bar);
95        mLowerContextView = (LinearLayout) decor.findViewById(
96                com.android.internal.R.id.lower_action_context_bar);
97        mAnimatorView = (ViewAnimator) decor.findViewById(
98                com.android.internal.R.id.action_bar_animator);
99
100        if (mActionView == null || mUpperContextView == null || mAnimatorView == null) {
101            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
102                    "with a compatible window decor layout");
103        }
104
105        mContextDisplayMode = mLowerContextView == null ?
106                CONTEXT_DISPLAY_NORMAL : CONTEXT_DISPLAY_SPLIT;
107    }
108
109    public void setCustomNavigationMode(View view) {
110        cleanupTabs();
111        mActionView.setCustomNavigationView(view);
112        mActionView.setCallback(null);
113    }
114
115    public void setDropdownNavigationMode(SpinnerAdapter adapter, NavigationCallback callback) {
116        setDropdownNavigationMode(adapter, callback, -1);
117    }
118
119    public void setDropdownNavigationMode(SpinnerAdapter adapter, NavigationCallback callback,
120            int defaultSelectedPosition) {
121        cleanupTabs();
122        mActionView.setNavigationMode(NAVIGATION_MODE_DROPDOWN_LIST);
123        mActionView.setDropdownAdapter(adapter);
124        if (defaultSelectedPosition >= 0) {
125            mActionView.setDropdownSelectedPosition(defaultSelectedPosition);
126        }
127        mActionView.setCallback(callback);
128    }
129
130    public void setStandardNavigationMode() {
131        cleanupTabs();
132        mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD);
133        mActionView.setCallback(null);
134    }
135
136    public void setStandardNavigationMode(CharSequence title) {
137        cleanupTabs();
138        setStandardNavigationMode(title, null);
139    }
140
141    public void setStandardNavigationMode(CharSequence title, CharSequence subtitle) {
142        cleanupTabs();
143        mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD);
144        mActionView.setTitle(title);
145        mActionView.setSubtitle(subtitle);
146        mActionView.setCallback(null);
147    }
148
149    public void setSelectedNavigationItem(int position) {
150        switch (mActionView.getNavigationMode()) {
151        case NAVIGATION_MODE_TABS:
152            selectTab(mTabs.get(position));
153            break;
154        case NAVIGATION_MODE_DROPDOWN_LIST:
155            mActionView.setDropdownSelectedPosition(position);
156            break;
157        default:
158            throw new IllegalStateException(
159                    "setSelectedNavigationItem not valid for current navigation mode");
160        }
161    }
162
163    public int getSelectedNavigationItem() {
164        switch (mActionView.getNavigationMode()) {
165        case NAVIGATION_MODE_TABS:
166            return mSelectedTab.getPosition();
167        case NAVIGATION_MODE_DROPDOWN_LIST:
168            return mActionView.getDropdownSelectedPosition();
169        default:
170            return -1;
171        }
172    }
173
174    private void cleanupTabs() {
175        if (mSelectedTab != null) {
176            selectTab(null);
177        }
178        if (!mTabs.isEmpty()) {
179            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
180                final FragmentTransaction trans = mActivity.openFragmentTransaction();
181                final int tabCount = mTabs.size();
182                for (int i = 0; i < tabCount; i++) {
183                    trans.remove(mTabs.get(i).getFragment());
184                }
185                trans.commit();
186            }
187            mTabs.clear();
188        }
189    }
190
191    public void setTitle(CharSequence title) {
192        mActionView.setTitle(title);
193    }
194
195    public void setSubtitle(CharSequence subtitle) {
196        mActionView.setSubtitle(subtitle);
197    }
198
199    public void setDisplayOptions(int options) {
200        mActionView.setDisplayOptions(options);
201    }
202
203    public void setDisplayOptions(int options, int mask) {
204        final int current = mActionView.getDisplayOptions();
205        mActionView.setDisplayOptions((options & mask) | (current & ~mask));
206    }
207
208    public void setBackgroundDrawable(Drawable d) {
209        mActionView.setBackgroundDrawable(d);
210    }
211
212    public View getCustomNavigationView() {
213        return mActionView.getCustomNavigationView();
214    }
215
216    public CharSequence getTitle() {
217        return mActionView.getTitle();
218    }
219
220    public CharSequence getSubtitle() {
221        return mActionView.getSubtitle();
222    }
223
224    public int getNavigationMode() {
225        return mActionView.getNavigationMode();
226    }
227
228    public int getDisplayOptions() {
229        return mActionView.getDisplayOptions();
230    }
231
232    public ActionMode startActionMode(ActionMode.Callback callback) {
233        if (mActionMode != null) {
234            mActionMode.finish();
235        }
236
237        // Don't wait for the close context mode animation to finish.
238        if (mClosingContext) {
239            mAnimatorView.clearAnimation();
240            mHandler.removeCallbacks(mCloseContext);
241            mCloseContext.run();
242        }
243
244        ActionMode mode = new ActionModeImpl(callback);
245        if (callback.onCreateActionMode(mode, mode.getMenu())) {
246            mode.invalidate();
247            mUpperContextView.initForMode(mode);
248            mAnimatorView.setDisplayedChild(CONTEXT_VIEW);
249            if (mLowerContextView != null) {
250                // TODO animate this
251                mLowerContextView.setVisibility(View.VISIBLE);
252            }
253            mActionMode = mode;
254            return mode;
255        }
256        return null;
257    }
258
259    private void configureTab(Tab tab, int position) {
260        final TabImpl tabi = (TabImpl) tab;
261        final boolean isFirstTab = mTabs.isEmpty();
262        final FragmentTransaction trans = mActivity.openFragmentTransaction();
263        final Fragment frag = tabi.getFragment();
264
265        tabi.setPosition(position);
266        mTabs.add(position, tabi);
267
268        if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
269            if (!frag.isAdded()) {
270                trans.add(mTabContainerViewId, frag);
271            }
272        }
273
274        if (isFirstTab) {
275            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
276                trans.show(frag);
277            } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
278                trans.add(mTabContainerViewId, frag);
279            }
280            mSelectedTab = tabi;
281        } else {
282            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
283                trans.hide(frag);
284            }
285        }
286        trans.commit();
287    }
288
289    @Override
290    public void addTab(Tab tab) {
291        mActionView.addTab(tab);
292        configureTab(tab, mTabs.size());
293    }
294
295    @Override
296    public void insertTab(Tab tab, int position) {
297        mActionView.insertTab(tab, position);
298        configureTab(tab, position);
299    }
300
301    @Override
302    public Tab newTab() {
303        return new TabImpl();
304    }
305
306    @Override
307    public void removeTab(Tab tab) {
308        removeTabAt(tab.getPosition());
309    }
310
311    @Override
312    public void removeTabAt(int position) {
313        mActionView.removeTabAt(position);
314        mTabs.remove(position);
315
316        final int newTabCount = mTabs.size();
317        for (int i = position; i < newTabCount; i++) {
318            mTabs.get(i).setPosition(i);
319        }
320
321        selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
322    }
323
324    @Override
325    public void setTabNavigationMode() {
326        mActionView.setNavigationMode(NAVIGATION_MODE_TABS);
327    }
328
329    @Override
330    public void setTabNavigationMode(int containerViewId) {
331        mTabContainerViewId = containerViewId;
332        setTabNavigationMode();
333    }
334
335    @Override
336    public void selectTab(Tab tab) {
337        if (mSelectedTab == tab) {
338            return;
339        }
340
341        mActionView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
342        final FragmentTransaction trans = mActivity.openFragmentTransaction();
343        if (mSelectedTab != null) {
344            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
345                trans.hide(mSelectedTab.getFragment());
346            } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
347                trans.remove(mSelectedTab.getFragment());
348            }
349        }
350        if (tab != null) {
351            if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) {
352                trans.show(tab.getFragment());
353            } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) {
354                trans.add(mTabContainerViewId, tab.getFragment());
355            }
356        }
357        mSelectedTab = (TabImpl) tab;
358        trans.commit();
359    }
360
361    /**
362     * @hide
363     */
364    public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback {
365        private ActionMode.Callback mCallback;
366        private MenuBuilder mMenu;
367        private WeakReference<View> mCustomView;
368
369        public ActionModeImpl(ActionMode.Callback callback) {
370            mCallback = callback;
371            mMenu = new MenuBuilder(mActionView.getContext());
372            mMenu.setCallback(this);
373        }
374
375        @Override
376        public Menu getMenu() {
377            return mMenu;
378        }
379
380        @Override
381        public void finish() {
382            if (mActionMode != this) {
383                // Not the active action mode - no-op
384                return;
385            }
386
387            mCallback.onDestroyActionMode(this);
388            mAnimatorView.setDisplayedChild(NORMAL_VIEW);
389
390            // Clear out the context mode views after the animation finishes
391            mClosingContext = true;
392            mHandler.postDelayed(mCloseContext, mAnimatorView.getOutAnimation().getDuration());
393
394            if (mLowerContextView != null && mLowerContextView.getVisibility() != View.GONE) {
395                // TODO Animate this
396                mLowerContextView.setVisibility(View.GONE);
397            }
398            mActionMode = null;
399        }
400
401        @Override
402        public void invalidate() {
403            if (mCallback.onPrepareActionMode(this, mMenu)) {
404                // Refresh content in both context views
405            }
406        }
407
408        @Override
409        public void setCustomView(View view) {
410            mUpperContextView.setCustomView(view);
411            mCustomView = new WeakReference<View>(view);
412        }
413
414        @Override
415        public void setSubtitle(CharSequence subtitle) {
416            mUpperContextView.setSubtitle(subtitle);
417        }
418
419        @Override
420        public void setTitle(CharSequence title) {
421            mUpperContextView.setTitle(title);
422        }
423
424        @Override
425        public CharSequence getTitle() {
426            return mUpperContextView.getTitle();
427        }
428
429        @Override
430        public CharSequence getSubtitle() {
431            return mUpperContextView.getSubtitle();
432        }
433
434        @Override
435        public View getCustomView() {
436            return mCustomView != null ? mCustomView.get() : null;
437        }
438
439        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
440            return mCallback.onActionItemClicked(this, item);
441        }
442
443        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
444        }
445
446        public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
447            if (!subMenu.hasVisibleItems()) {
448                return true;
449            }
450
451            new MenuPopupHelper(mActivity, subMenu).show();
452            return true;
453        }
454
455        public void onCloseSubMenu(SubMenuBuilder menu) {
456        }
457
458        public void onMenuModeChange(MenuBuilder menu) {
459        }
460    }
461
462    /**
463     * @hide
464     */
465    public class TabImpl extends ActionBar.Tab {
466        private Fragment mFragment;
467        private Drawable mIcon;
468        private CharSequence mText;
469        private int mPosition;
470
471        @Override
472        public Fragment getFragment() {
473            return mFragment;
474        }
475
476        @Override
477        public Drawable getIcon() {
478            return mIcon;
479        }
480
481        @Override
482        public int getPosition() {
483            return mPosition;
484        }
485
486        public void setPosition(int position) {
487            mPosition = position;
488        }
489
490        @Override
491        public CharSequence getText() {
492            return mText;
493        }
494
495        @Override
496        public void setFragment(Fragment fragment) {
497            mFragment = fragment;
498        }
499
500        @Override
501        public void setIcon(Drawable icon) {
502            mIcon = icon;
503        }
504
505        @Override
506        public void setText(CharSequence text) {
507            mText = text;
508        }
509
510        @Override
511        public void select() {
512            selectTab(this);
513        }
514    }
515}
516