FrameworkActionBarWrapper.java revision 97c0679b1a4e650191203d1a03159b3dec67252e
1/*
2 * Copyright (C) 2014 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.layoutlib.bridge.bars;
18
19import com.android.ide.common.rendering.api.ActionBarCallback;
20import com.android.ide.common.rendering.api.RenderResources;
21import com.android.ide.common.rendering.api.ResourceValue;
22import com.android.internal.R;
23import com.android.internal.app.ToolbarActionBar;
24import com.android.internal.app.WindowDecorActionBar;
25import com.android.internal.view.menu.MenuBuilder;
26import com.android.internal.widget.ActionBarAccessor;
27import com.android.internal.widget.ActionBarView;
28import com.android.internal.widget.DecorToolbar;
29import com.android.layoutlib.bridge.android.BridgeContext;
30import com.android.layoutlib.bridge.impl.ResourceHelper;
31
32import android.annotation.NonNull;
33import android.annotation.Nullable;
34import android.app.ActionBar;
35import android.app.ActionBar.Tab;
36import android.app.ActionBar.TabListener;
37import android.app.FragmentTransaction;
38import android.content.Context;
39import android.content.res.Resources;
40import android.graphics.drawable.Drawable;
41import android.view.MenuInflater;
42import android.view.View;
43import android.view.ViewGroup;
44import android.view.WindowCallback;
45import android.widget.ActionMenuPresenter;
46import android.widget.ActionMenuView;
47import android.widget.Toolbar;
48import android.widget.Toolbar_Accessor;
49
50import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
51import static com.android.resources.ResourceType.MENU;
52
53/**
54 * A common API to access {@link ToolbarActionBar} and {@link WindowDecorActionBar}.
55 */
56public abstract class FrameworkActionBarWrapper {
57
58    @NonNull protected ActionBar mActionBar;
59    @NonNull protected ActionBarCallback mCallback;
60    @NonNull protected BridgeContext mContext;
61
62    /**
63     * Returns a wrapper around different implementations of the Action Bar to provide a common API.
64     *
65     * @param decorContent the top level view returned by inflating
66     *                     ?attr/windowActionBarFullscreenDecorLayout
67     */
68    @NonNull
69    public static FrameworkActionBarWrapper getActionBarWrapper(@NonNull BridgeContext context,
70            @NonNull ActionBarCallback callback, @NonNull View decorContent) {
71        View view = decorContent.findViewById(R.id.action_bar);
72        if (view instanceof Toolbar) {
73            return new ToolbarWrapper(context, callback, (Toolbar) view);
74        } else if (view instanceof ActionBarView) {
75            return new WindowActionBarWrapper(context, callback, decorContent,
76                    (ActionBarView) view);
77        } else {
78            throw new IllegalStateException("Can't make an action bar out of " +
79                    view.getClass().getSimpleName());
80        }
81    }
82
83    FrameworkActionBarWrapper(@NonNull BridgeContext context, @NonNull ActionBarCallback callback,
84            @NonNull ActionBar actionBar) {
85        mActionBar = actionBar;
86        mCallback = callback;
87        mContext = context;
88    }
89
90    /** A call to setup any custom properties. */
91    protected void setupActionBar() {
92        // Nothing to do here.
93    }
94
95    public void setTitle(CharSequence title) {
96        mActionBar.setTitle(title);
97    }
98
99    public void setSubTitle(CharSequence subTitle) {
100        if (subTitle != null) {
101            mActionBar.setSubtitle(subTitle);
102        }
103    }
104
105    public void setHomeAsUp(boolean homeAsUp) {
106        mActionBar.setDisplayHomeAsUpEnabled(homeAsUp);
107    }
108
109    public void setIcon(String icon) {
110        // Nothing to do.
111    }
112
113    protected boolean isSplit() {
114        return getDecorToolbar().isSplit();
115    }
116
117    protected boolean isOverflowPopupNeeded() {
118        return mCallback.isOverflowPopupNeeded();
119    }
120
121    /**
122     * Gets the menus to add to the action bar from the callback, resolves them, inflates them and
123     * adds them to the action bar.
124     */
125    protected void inflateMenus() {
126        MenuInflater inflater = new MenuInflater(getActionMenuContext());
127        MenuBuilder menuBuilder = getMenuBuilder();
128        for (String name : mCallback.getMenuIdNames()) {
129            int id;
130            if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
131                // Framework menu.
132                name = name.substring(ANDROID_NS_NAME_PREFIX.length());
133                id = mContext.getFrameworkResourceValue(MENU, name, -1);
134            } else {
135                // Project menu.
136                id = mContext.getProjectResourceValue(MENU, name, -1);
137            }
138            if (id > -1) {
139                inflater.inflate(id, menuBuilder);
140            }
141        }
142    }
143
144    /**
145     * The context used for the ActionBar and the menus in the ActionBarView.
146     */
147    @NonNull
148    protected Context getActionMenuContext() {
149        return mActionBar.getThemedContext();
150    }
151
152    /**
153     * The context used to inflate the popup menu.
154     */
155    @NonNull
156    abstract Context getPopupContext();
157
158    /**
159     * The Menu in which to inflate the user's menus.
160     */
161    @NonNull
162    abstract MenuBuilder getMenuBuilder();
163
164    @Nullable
165    abstract ActionMenuPresenter getActionMenuPresenter();
166
167    /**
168     * Framework's wrapper over two ActionBar implementations.
169     */
170    @NonNull
171    abstract DecorToolbar getDecorToolbar();
172
173    abstract int getMenuPopupElevation();
174
175    /**
176     * Margin between the menu popup and the action bar.
177     */
178    abstract int getMenuPopupMargin();
179
180    // ---- The implementations ----
181
182    /**
183     * Material theme uses {@link Toolbar} as the action bar. This wrapper provides access to
184     * Toolbar using a common API.
185     */
186    private static class ToolbarWrapper extends FrameworkActionBarWrapper {
187
188        @NonNull
189        private final Toolbar mToolbar;  // This is the view.
190
191        ToolbarWrapper(@NonNull BridgeContext context, @NonNull ActionBarCallback callback,
192                @NonNull Toolbar toolbar) {
193            super(context, callback, new ToolbarActionBar(toolbar, "", new WindowCallback()));
194            mToolbar = toolbar;
195        }
196
197        @Override
198        protected void inflateMenus() {
199            super.inflateMenus();
200            // Inflating the menus isn't enough. ActionMenuPresenter needs to be initialized too.
201            MenuBuilder menu = getMenuBuilder();
202            DecorToolbar decorToolbar = getDecorToolbar();
203            // Setting a menu different from the above initializes the presenter.
204            decorToolbar.setMenu(new MenuBuilder(getActionMenuContext()), null);
205            // ActionMenuView needs to be recreated to be able to set the menu back.
206            ActionMenuPresenter presenter = getActionMenuPresenter();
207            if (presenter != null) {
208                presenter.setMenuView(new ActionMenuView(getPopupContext()));
209            }
210            decorToolbar.setMenu(menu, null);
211        }
212
213        @NonNull
214        @Override
215        Context getPopupContext() {
216            return Toolbar_Accessor.getPopupContext(mToolbar);
217        }
218
219        @NonNull
220        @Override
221        MenuBuilder getMenuBuilder() {
222            return (MenuBuilder) mToolbar.getMenu();
223        }
224
225        @Nullable
226        @Override
227        ActionMenuPresenter getActionMenuPresenter() {
228            return Toolbar_Accessor.getActionMenuPresenter(mToolbar);
229        }
230
231        @NonNull
232        @Override
233        DecorToolbar getDecorToolbar() {
234            return mToolbar.getWrapper();
235        }
236
237        @Override
238        int getMenuPopupElevation() {
239            return 10;
240        }
241
242        @Override
243        int getMenuPopupMargin() {
244            return 0;
245        }
246    }
247
248    /**
249     * Holo theme uses {@link WindowDecorActionBar} as the action bar. This wrapper provides
250     * access to it using a common API.
251     */
252    private static class WindowActionBarWrapper extends FrameworkActionBarWrapper {
253
254        @NonNull private final WindowDecorActionBar mActionBar;
255        @NonNull private final ActionBarView mActionBarView;
256        @NonNull private final View mDecorContentRoot;
257        private MenuBuilder mMenuBuilder;
258
259        public WindowActionBarWrapper(@NonNull BridgeContext context,
260                @NonNull ActionBarCallback callback, @NonNull View decorContentRoot,
261                @NonNull ActionBarView actionBarView) {
262            super(context, callback, new WindowDecorActionBar(decorContentRoot));
263            mActionBarView = actionBarView;
264            mActionBar = (WindowDecorActionBar) super.mActionBar;
265            mDecorContentRoot = decorContentRoot;
266        }
267
268        @Override
269        protected void setupActionBar() {
270
271            // Set the navigation mode.
272            int navMode = mCallback.getNavigationMode();
273            mActionBar.setNavigationMode(navMode);
274            //noinspection deprecation
275            if (navMode == ActionBar.NAVIGATION_MODE_TABS) {
276                setupTabs(3);
277            }
278
279            // Set action bar to be split, if needed.
280            ViewGroup splitView = (ViewGroup) mDecorContentRoot.findViewById(R.id.split_action_bar);
281            if (splitView != null) {
282                mActionBarView.setSplitView(splitView);
283                Resources res = mContext.getResources();
284                boolean split = res.getBoolean(R.bool.split_action_bar_is_narrow)
285                        && mCallback.getSplitActionBarWhenNarrow();
286                mActionBarView.setSplitToolbar(split);
287            }
288        }
289
290        @Override
291        public void setIcon(String icon) {
292            // Set the icon only if the action bar doesn't specify an icon.
293            if (!mActionBar.hasIcon() && icon != null) {
294                Drawable iconDrawable = getDrawable(icon, false);
295                if (iconDrawable != null) {
296                    mActionBar.setIcon(iconDrawable);
297                }
298            }
299        }
300
301        @Override
302        protected void inflateMenus() {
303            super.inflateMenus();
304            // The super implementation doesn't set the menu on the view. Set it here.
305            mActionBarView.setMenu(getMenuBuilder(), null);
306        }
307
308        @NonNull
309        @Override
310        Context getPopupContext() {
311            return getActionMenuContext();
312        }
313
314        @NonNull
315        @Override
316        MenuBuilder getMenuBuilder() {
317            if (mMenuBuilder == null) {
318                mMenuBuilder = new MenuBuilder(getActionMenuContext());
319            }
320            return mMenuBuilder;
321        }
322
323        @Nullable
324        @Override
325        ActionMenuPresenter getActionMenuPresenter() {
326            return ActionBarAccessor.getActionMenuPresenter(mActionBarView);
327        }
328
329        @NonNull
330        @Override
331        ActionBarView getDecorToolbar() {
332            return mActionBarView;
333        }
334
335        @Override
336        int getMenuPopupElevation() {
337            return 0;
338        }
339
340        @Override
341        int getMenuPopupMargin() {
342            return -FrameworkActionBar.getPixelValue("10dp", mContext.getMetrics());
343        }
344
345        // TODO: Use an adapter, like List View to set up tabs.
346        @SuppressWarnings("deprecation")  // For Tab
347        private void setupTabs(int num) {
348            for (int i = 1; i <= num; i++) {
349                Tab tab = mActionBar.newTab().setText("Tab" + i).setTabListener(new TabListener() {
350                    @Override
351                    public void onTabUnselected(Tab t, FragmentTransaction ft) {
352                        // pass
353                    }
354                    @Override
355                    public void onTabSelected(Tab t, FragmentTransaction ft) {
356                        // pass
357                    }
358                    @Override
359                    public void onTabReselected(Tab t, FragmentTransaction ft) {
360                        // pass
361                    }
362                });
363                mActionBar.addTab(tab);
364            }
365        }
366
367        @Nullable
368        private Drawable getDrawable(@NonNull String name, boolean isFramework) {
369            RenderResources res = mContext.getRenderResources();
370            ResourceValue value = res.findResValue(name, isFramework);
371            value = res.resolveResValue(value);
372            if (value != null) {
373                return ResourceHelper.getDrawable(value, mContext);
374            }
375            return null;
376        }
377
378    }
379}
380