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