ActionBarImpl.java revision 2b6230e0de4bac2829ac27b19e95ba75c3da82b4
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 static final int TAB_SWITCH_SHOW_HIDE = 0;
57    private static final int TAB_SWITCH_ADD_REMOVE = 1;
58
59    private Context mContext;
60    private Activity mActivity;
61    private Dialog mDialog;
62
63    private ViewAnimator mAnimatorView;
64    private ActionBarView mActionView;
65    private ActionBarContextView mUpperContextView;
66    private LinearLayout mLowerContextView;
67
68    private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
69
70    private int mTabContainerViewId = android.R.id.content;
71    private TabImpl mSelectedTab;
72    private int mTabSwitchMode = TAB_SWITCH_ADD_REMOVE;
73
74    private ActionMode mActionMode;
75
76    private static final int CONTEXT_DISPLAY_NORMAL = 0;
77    private static final int CONTEXT_DISPLAY_SPLIT = 1;
78
79    private int mContextDisplayMode;
80
81    private boolean mClosingContext;
82
83    final Handler mHandler = new Handler();
84    final Runnable mCloseContext = new Runnable() {
85        public void run() {
86            mUpperContextView.closeMode();
87            if (mLowerContextView != null) {
88                mLowerContextView.removeAllViews();
89            }
90            mClosingContext = false;
91        }
92    };
93
94    public ActionBarImpl(Activity activity) {
95        mActivity = activity;
96        init(activity.getWindow().getDecorView());
97    }
98
99    public ActionBarImpl(Dialog dialog) {
100        mDialog = dialog;
101        init(dialog.getWindow().getDecorView());
102    }
103
104    private void init(View decor) {
105        mContext = decor.getContext();
106        mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
107        mUpperContextView = (ActionBarContextView) decor.findViewById(
108                com.android.internal.R.id.action_context_bar);
109        mLowerContextView = (LinearLayout) decor.findViewById(
110                com.android.internal.R.id.lower_action_context_bar);
111        mAnimatorView = (ViewAnimator) decor.findViewById(
112                com.android.internal.R.id.action_bar_animator);
113
114        if (mActionView == null || mUpperContextView == null || mAnimatorView == null) {
115            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
116                    "with a compatible window decor layout");
117        }
118
119        mActionView.setContextView(mUpperContextView);
120        mContextDisplayMode = mLowerContextView == null ?
121                CONTEXT_DISPLAY_NORMAL : CONTEXT_DISPLAY_SPLIT;
122    }
123
124    @Override
125    public void setTitle(int resId) {
126        setTitle(mContext.getString(resId));
127    }
128
129    @Override
130    public void setSubtitle(int resId) {
131        setSubtitle(mContext.getString(resId));
132    }
133
134    public void setCustomNavigationMode(View view) {
135        cleanupTabs();
136        mActionView.setCustomNavigationView(view);
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        mActionView.setNavigationMode(NAVIGATION_MODE_DROPDOWN_LIST);
148        mActionView.setDropdownAdapter(adapter);
149        if (defaultSelectedPosition >= 0) {
150            mActionView.setDropdownSelectedPosition(defaultSelectedPosition);
151        }
152        mActionView.setCallback(callback);
153    }
154
155    public void setStandardNavigationMode() {
156        cleanupTabs();
157        mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD);
158        mActionView.setCallback(null);
159    }
160
161    public void setSelectedNavigationItem(int position) {
162        switch (mActionView.getNavigationMode()) {
163        case NAVIGATION_MODE_TABS:
164            selectTab(mTabs.get(position));
165            break;
166        case NAVIGATION_MODE_DROPDOWN_LIST:
167            mActionView.setDropdownSelectedPosition(position);
168            break;
169        default:
170            throw new IllegalStateException(
171                    "setSelectedNavigationItem not valid for current navigation mode");
172        }
173    }
174
175    public int getSelectedNavigationItem() {
176        switch (mActionView.getNavigationMode()) {
177        case NAVIGATION_MODE_TABS:
178            return mSelectedTab.getPosition();
179        case NAVIGATION_MODE_DROPDOWN_LIST:
180            return mActionView.getDropdownSelectedPosition();
181        default:
182            return -1;
183        }
184    }
185
186    private void cleanupTabs() {
187        if (mSelectedTab != null) {
188            selectTab(null);
189        }
190        mTabs.clear();
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 boolean isFirstTab = mTabs.isEmpty();
264        final ActionBar.TabListener callback = tabi.getCallback();
265
266        if (callback == null) {
267            throw new IllegalStateException("Action Bar Tab must have a Callback");
268        }
269
270        tabi.setPosition(position);
271        mTabs.add(position, tabi);
272
273        if (isFirstTab) {
274            final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction();
275            mSelectedTab = tabi;
276            callback.onTabSelected(tab, trans);
277            if (!trans.isEmpty()) {
278                trans.commit();
279            }
280        }
281    }
282
283    @Override
284    public void addTab(Tab tab) {
285        mActionView.addTab(tab);
286        configureTab(tab, mTabs.size());
287    }
288
289    @Override
290    public void addTab(Tab tab, int position) {
291        mActionView.addTab(tab, position);
292        configureTab(tab, position);
293    }
294
295    @Override
296    public Tab newTab() {
297        return new TabImpl();
298    }
299
300    @Override
301    public void removeTab(Tab tab) {
302        removeTabAt(tab.getPosition());
303    }
304
305    @Override
306    public void removeTabAt(int position) {
307        mActionView.removeTabAt(position);
308        mTabs.remove(position);
309
310        final int newTabCount = mTabs.size();
311        for (int i = position; i < newTabCount; i++) {
312            mTabs.get(i).setPosition(i);
313        }
314
315        selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
316    }
317
318    @Override
319    public void setTabNavigationMode() {
320        if (mActivity == null) {
321            throw new IllegalStateException(
322                    "Tab navigation mode cannot be used outside of an Activity");
323        }
324        mActionView.setNavigationMode(NAVIGATION_MODE_TABS);
325    }
326
327    @Override
328    public void selectTab(Tab tab) {
329        if (mSelectedTab == tab) {
330            return;
331        }
332
333        mActionView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
334        final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction();
335        if (mSelectedTab != null) {
336            mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans);
337        }
338        mSelectedTab = (TabImpl) tab;
339        if (mSelectedTab != null) {
340            mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans);
341        }
342
343        if (!trans.isEmpty()) {
344            trans.commit();
345        }
346    }
347
348    @Override
349    public Tab getSelectedTab() {
350        return mSelectedTab;
351    }
352
353    @Override
354    public int getHeight() {
355        return mActionView.getHeight();
356    }
357
358    @Override
359    public void show() {
360        // TODO animate!
361        mAnimatorView.setVisibility(View.VISIBLE);
362    }
363
364    @Override
365    public void hide() {
366        // TODO animate!
367        mAnimatorView.setVisibility(View.GONE);
368    }
369
370    public boolean isShowing() {
371        return mAnimatorView.getVisibility() == View.VISIBLE;
372    }
373
374    /**
375     * @hide
376     */
377    public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback {
378        private ActionMode.Callback mCallback;
379        private MenuBuilder mMenu;
380        private WeakReference<View> mCustomView;
381
382        public ActionModeImpl(ActionMode.Callback callback) {
383            mCallback = callback;
384            mMenu = new MenuBuilder(mActionView.getContext())
385                    .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
386            mMenu.setCallback(this);
387        }
388
389        @Override
390        public MenuInflater getMenuInflater() {
391            return new MenuInflater(mContext);
392        }
393
394        @Override
395        public Menu getMenu() {
396            return mMenu;
397        }
398
399        @Override
400        public void finish() {
401            if (mActionMode != this) {
402                // Not the active action mode - no-op
403                return;
404            }
405
406            mCallback.onDestroyActionMode(this);
407            mAnimatorView.setDisplayedChild(NORMAL_VIEW);
408
409            // Clear out the context mode views after the animation finishes
410            mClosingContext = true;
411            mHandler.postDelayed(mCloseContext, mAnimatorView.getOutAnimation().getDuration());
412
413            if (mLowerContextView != null && mLowerContextView.getVisibility() != View.GONE) {
414                // TODO Animate this
415                mLowerContextView.setVisibility(View.GONE);
416            }
417            mActionMode = null;
418        }
419
420        @Override
421        public void invalidate() {
422            if (mCallback.onPrepareActionMode(this, mMenu)) {
423                // Refresh content in both context views
424            }
425        }
426
427        @Override
428        public void setCustomView(View view) {
429            mUpperContextView.setCustomView(view);
430            mCustomView = new WeakReference<View>(view);
431        }
432
433        @Override
434        public void setSubtitle(CharSequence subtitle) {
435            mUpperContextView.setSubtitle(subtitle);
436        }
437
438        @Override
439        public void setTitle(CharSequence title) {
440            mUpperContextView.setTitle(title);
441        }
442
443        @Override
444        public void setTitle(int resId) {
445            setTitle(mActivity.getString(resId));
446        }
447
448        @Override
449        public void setSubtitle(int resId) {
450            setSubtitle(mActivity.getString(resId));
451        }
452
453        @Override
454        public CharSequence getTitle() {
455            return mUpperContextView.getTitle();
456        }
457
458        @Override
459        public CharSequence getSubtitle() {
460            return mUpperContextView.getSubtitle();
461        }
462
463        @Override
464        public View getCustomView() {
465            return mCustomView != null ? mCustomView.get() : null;
466        }
467
468        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
469            return mCallback.onActionItemClicked(this, item);
470        }
471
472        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
473        }
474
475        public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
476            if (!subMenu.hasVisibleItems()) {
477                return true;
478            }
479
480            new MenuPopupHelper(mContext, subMenu).show();
481            return true;
482        }
483
484        public void onCloseSubMenu(SubMenuBuilder menu) {
485        }
486
487        public void onMenuModeChange(MenuBuilder menu) {
488            invalidate();
489            mUpperContextView.showOverflowMenu();
490        }
491    }
492
493    /**
494     * @hide
495     */
496    public class TabImpl extends ActionBar.Tab {
497        private ActionBar.TabListener mCallback;
498        private Object mTag;
499        private Drawable mIcon;
500        private CharSequence mText;
501        private int mPosition;
502        private View mCustomView;
503
504        @Override
505        public Object getTag() {
506            return mTag;
507        }
508
509        @Override
510        public void setTag(Object tag) {
511            mTag = tag;
512        }
513
514        public ActionBar.TabListener getCallback() {
515            return mCallback;
516        }
517
518        @Override
519        public void setTabListener(ActionBar.TabListener callback) {
520            mCallback = callback;
521        }
522
523        @Override
524        public View getCustomView() {
525            return mCustomView;
526        }
527
528        @Override
529        public void setCustomView(View view) {
530            mCustomView = view;
531        }
532
533        @Override
534        public Drawable getIcon() {
535            return mIcon;
536        }
537
538        @Override
539        public int getPosition() {
540            return mPosition;
541        }
542
543        public void setPosition(int position) {
544            mPosition = position;
545        }
546
547        @Override
548        public CharSequence getText() {
549            return mText;
550        }
551
552        @Override
553        public void setIcon(Drawable icon) {
554            mIcon = icon;
555        }
556
557        @Override
558        public void setText(CharSequence text) {
559            mText = text;
560        }
561
562        @Override
563        public void select() {
564            selectTab(this);
565        }
566    }
567}
568