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