From 5ba2f230faa355eb9bc1e90f6c48eeeb437f390c Mon Sep 17 00:00:00 2001 From: Deepanshu Gupta Date: Fri, 18 Apr 2014 12:32:38 -0700 Subject: [PATCH] Add view cookies for action bar menus. [DO NOT MERGE] The change adds the view cookies for the menus rendered in the action bar. This enables the IDE to map the menu to the relevant XML Tag in the menu xml and show the highlighting accordingly. The change also contains a bugfix where a method wasn't renamed properly. Change-Id: Idcfc263a8ebe0a4f25afa3a1eb085fa628fd03ca (cherry-picked from commit 1001961f904bac5294aaf73a47c2497aa764bf7f) --- core/java/android/view/MenuInflater.java | 36 +++++++-- .../android/internal/view/menu/MenuBuilder.java | 13 ++- .../bridge/src/android/view/BridgeInflater.java | 94 ++++++++++++---------- .../src/android/view/MenuInflater_Delegate.java | 75 +++++++++++++++++ .../internal/view/menu/BridgeMenuItemImpl.java | 47 +++++++++++ .../internal/view/menu/MenuBuilder_Delegate.java | 38 +++++++++ .../layoutlib/bridge/impl/RenderSessionImpl.java | 40 ++++++++- .../android/tools/layoutlib/create/CreateInfo.java | 2 + 8 files changed, 292 insertions(+), 53 deletions(-) create mode 100644 tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java create mode 100644 tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java create mode 100644 tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java index a7ee12b8558b..71296fa61965 100644 --- a/core/java/android/view/MenuInflater.java +++ b/core/java/android/view/MenuInflater.java @@ -161,6 +161,7 @@ public class MenuInflater { } else if (tagName.equals(XML_MENU)) { // A menu start tag denotes a submenu for an item SubMenu subMenu = menuState.addSubMenuItem(); + registerMenu(subMenu, attrs); // Parse the submenu into returned SubMenu parseMenu(parser, attrs, subMenu); @@ -183,9 +184,9 @@ public class MenuInflater { if (!menuState.hasAddedItem()) { if (menuState.itemActionProvider != null && menuState.itemActionProvider.hasSubMenu()) { - menuState.addSubMenuItem(); + registerMenu(menuState.addSubMenuItem(), attrs); } else { - menuState.addItem(); + registerMenu(menuState.addItem(), attrs); } } } else if (tagName.equals(XML_MENU)) { @@ -200,7 +201,30 @@ public class MenuInflater { eventType = parser.next(); } } - + + /** + * The method is a hook for layoutlib to do its magic. + * Nothing is needed outside of LayoutLib. However, it should not be deleted because it + * appears to do nothing. + */ + private void registerMenu(@SuppressWarnings("unused") MenuItem item, + @SuppressWarnings("unused") AttributeSet set) { + } + + /** + * The method is a hook for layoutlib to do its magic. + * Nothing is needed outside of LayoutLib. However, it should not be deleted because it + * appears to do nothing. + */ + private void registerMenu(@SuppressWarnings("unused") SubMenu subMenu, + @SuppressWarnings("unused") AttributeSet set) { + } + + // Needed by layoutlib. + /*package*/ Context getContext() { + return mContext; + } + private static class InflatedOnMenuItemClickListener implements MenuItem.OnMenuItemClickListener { private static final Class[] PARAM_TYPES = new Class[] { MenuItem.class }; @@ -446,9 +470,11 @@ public class MenuInflater { } } - public void addItem() { + public MenuItem addItem() { itemAdded = true; - setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle)); + MenuItem item = menu.add(groupId, itemId, itemCategoryOrder, itemTitle); + setItem(item); + return item; } public SubMenu addSubMenuItem() { diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 195a00d9eca7..5464284569b0 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -392,8 +392,8 @@ public class MenuBuilder implements Menu { private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) { final int ordering = getOrdering(categoryOrder); - final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder, - ordering, title, mDefaultShowAsAction); + final MenuItemImpl item = createNewMenuItem(group, id, categoryOrder, ordering, title, + mDefaultShowAsAction); if (mCurrentMenuInfo != null) { // Pass along the current menu info @@ -405,7 +405,14 @@ public class MenuBuilder implements Menu { return item; } - + + // Layoutlib overrides this method to return its custom implementation of MenuItemImpl + private MenuItemImpl createNewMenuItem(int group, int id, int categoryOrder, int ordering, + CharSequence title, int defaultShowAsAction) { + return new MenuItemImpl(this, group, id, categoryOrder, ordering, title, + defaultShowAsAction); + } + public MenuItem add(CharSequence title) { return addInternal(0, 0, 0, title); } diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index 941f1ce6ef22..a2e93a7fb891 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -32,10 +32,6 @@ import org.xmlpull.v1.XmlPullParser; import android.content.Context; import android.util.AttributeSet; -import android.view.InflateException; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; import java.io.File; @@ -154,6 +150,9 @@ public final class BridgeInflater extends LayoutInflater { @Override public View inflate(int resource, ViewGroup root) { Context context = getContext(); + if (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } if (context instanceof BridgeContext) { BridgeContext bridgeContext = (BridgeContext)context; @@ -216,43 +215,16 @@ public final class BridgeInflater extends LayoutInflater { } private void setupViewInContext(View view, AttributeSet attrs) { - if (getContext() instanceof BridgeContext) { - BridgeContext bc = (BridgeContext) getContext(); - if (attrs instanceof BridgeXmlBlockParser) { - BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs; - - // get the view key - Object viewKey = parser.getViewCookie(); - - if (viewKey == null) { - int currentDepth = parser.getDepth(); - - // test whether we are in an included file or in a adapter binding view. - BridgeXmlBlockParser previousParser = bc.getPreviousParser(); - if (previousParser != null) { - // looks like we inside an embedded layout. - // only apply the cookie of the calling node () if we are at the - // top level of the embedded layout. If there is a merge tag, then - // skip it and look for the 2nd level - int testDepth = mIsInMerge ? 2 : 1; - if (currentDepth == testDepth) { - viewKey = previousParser.getViewCookie(); - // if we are in a merge, wrap the cookie in a MergeCookie. - if (viewKey != null && mIsInMerge) { - viewKey = new MergeCookie(viewKey); - } - } - } else if (mResourceReference != null && currentDepth == 1) { - // else if there's a resource reference, this means we are in an adapter - // binding case. Set the resource ref as the view cookie only for the top - // level view. - viewKey = mResourceReference; - } - } - - if (viewKey != null) { - bc.addViewKey(view, viewKey); - } + Context context = getContext(); + if (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } + if (context instanceof BridgeContext) { + BridgeContext bc = (BridgeContext) context; + // get the view key + Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge); + if (viewKey != null) { + bc.addViewKey(view, viewKey); } } } @@ -269,4 +241,44 @@ public final class BridgeInflater extends LayoutInflater { public LayoutInflater cloneInContext(Context newContext) { return new BridgeInflater(this, newContext); } + + /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc, + ResourceReference resourceReference, boolean isInMerge) { + + if (!(attrs instanceof BridgeXmlBlockParser)) { + return null; + } + BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs); + + // get the view key + Object viewKey = parser.getViewCookie(); + + if (viewKey == null) { + int currentDepth = parser.getDepth(); + + // test whether we are in an included file or in a adapter binding view. + BridgeXmlBlockParser previousParser = bc.getPreviousParser(); + if (previousParser != null) { + // looks like we are inside an embedded layout. + // only apply the cookie of the calling node () if we are at the + // top level of the embedded layout. If there is a merge tag, then + // skip it and look for the 2nd level + int testDepth = isInMerge ? 2 : 1; + if (currentDepth == testDepth) { + viewKey = previousParser.getViewCookie(); + // if we are in a merge, wrap the cookie in a MergeCookie. + if (viewKey != null && isInMerge) { + viewKey = new MergeCookie(viewKey); + } + } + } else if (resourceReference != null && currentDepth == 1) { + // else if there's a resource reference, this means we are in an adapter + // binding case. Set the resource ref as the view cookie only for the top + // level view. + viewKey = resourceReference; + } + } + + return viewKey; + } } diff --git a/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java new file mode 100644 index 000000000000..e34ad38be754 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.content.Context; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.internal.view.menu.BridgeMenuItemImpl; +import com.android.internal.view.menu.MenuView; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.util.AttributeSet; + +/** + * Delegate used to provide new implementation of a select few methods of {@link MenuInflater} + *

+ * Through the layoutlib_create tool, the original methods of MenuInflater have been + * replaced by calls to methods of the same name in this delegate class. + *

+ * The main purpose of the class is to get the view key from the menu xml parser and add it to + * the menu item. The view key is used by the IDE to match the individual view elements to the + * corresponding xml tag in the menu/layout file. + *

+ * For Menus, the views may be reused and the {@link MenuItem} is a better object to hold the + * view key than the {@link MenuView.ItemView}. At the time of computation of the rest of {@link + * ViewInfo}, we check the corresponding view key in the menu item for the view and add it + */ +public class MenuInflater_Delegate { + + @LayoutlibDelegate + /*package*/ static void registerMenu(MenuInflater thisInflater, MenuItem menuItem, + AttributeSet attrs) { + if (menuItem instanceof BridgeMenuItemImpl) { + Context context = thisInflater.getContext(); + if (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } + if (context instanceof BridgeContext) { + Object viewKey = BridgeInflater.getViewKeyFromParser( + attrs, ((BridgeContext) context), null, false); + ((BridgeMenuItemImpl) menuItem).setViewCookie(viewKey); + return; + } + } + // This means that Bridge did not take over the instantiation of some object properly. + // This is most likely a bug in the LayoutLib code. + Bridge.getLog().warning(LayoutLog.TAG_BROKEN, + "Action Bar Menu rendering may be incorrect.", null); + + } + + @LayoutlibDelegate + /*package*/ static void registerMenu(MenuInflater thisInflater, SubMenu subMenu, + AttributeSet parser) { + registerMenu(thisInflater, subMenu.getItem(), parser); + } + +} diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java new file mode 100644 index 000000000000..4bef424f506c --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.view.menu; + +/** + * An extension of the {@link MenuItemImpl} to store the view cookie also. + */ +public class BridgeMenuItemImpl extends MenuItemImpl { + + /** + * An object returned by the IDE that helps mapping each View to the corresponding XML tag in + * the layout. For Menus, we store this cookie here and attach it to the corresponding view + * at the time of rendering. + */ + private Object viewCookie; + + /** + * Instantiates this menu item. + */ + BridgeMenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering, + CharSequence title, int showAsAction) { + super(menu, group, id, categoryOrder, ordering, title, showAsAction); + } + + + public Object getViewCookie() { + return viewCookie; + } + + public void setViewCookie(Object viewCookie) { + this.viewCookie = viewCookie; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java new file mode 100644 index 000000000000..505fb8172ceb --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.view.menu; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate used to provide new implementation of a select few methods of {@link MenuBuilder} + *

+ * Through the layoutlib_create tool, the original methods of {@code MenuBuilder} have been + * replaced by calls to methods of the same name in this delegate class. + */ +public class MenuBuilder_Delegate { + /** + * The method overrides the instantiation of the {@link MenuItemImpl} with an instance of + * {@link BridgeMenuItemImpl} so that view cookies may be stored. + */ + @LayoutlibDelegate + /*package*/ static MenuItemImpl createNewMenuItem(MenuBuilder thisMenu, int group, int id, + int categoryOrder, int ordering, CharSequence title, int defaultShowAsAction) { + return new BridgeMenuItemImpl(thisMenu, group, id, categoryOrder, ordering, title, + defaultShowAsAction); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index afcadef33adf..89ea0d0bc55e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -28,7 +28,6 @@ import com.android.ide.common.rendering.api.HardwareConfig; import com.android.ide.common.rendering.api.IAnimationListener; import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; -import com.android.ide.common.rendering.api.RenderParams; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.ResourceReference; @@ -39,6 +38,12 @@ import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.SessionParams.RenderingMode; import com.android.ide.common.rendering.api.ViewInfo; import com.android.internal.util.XmlUtils; +import com.android.internal.view.menu.ActionMenuItemView; +import com.android.internal.view.menu.BridgeMenuItemImpl; +import com.android.internal.view.menu.IconMenuItemView; +import com.android.internal.view.menu.ListMenuItemView; +import com.android.internal.view.menu.MenuItemImpl; +import com.android.internal.view.menu.MenuView; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; @@ -573,7 +578,8 @@ public class RenderSessionImpl extends RenderAction { mViewRoot.draw(mCanvas); } - mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), false); + mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), + false); // success! return SUCCESS.createResult(); @@ -1468,13 +1474,13 @@ public class RenderSessionImpl extends RenderAction { ViewInfo result; if (isContentFrame) { result = new ViewInfo(view.getClass().getName(), - getContext().getViewKey(view), + getViewKey(view), view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset, view, view.getLayoutParams()); } else { result = new SystemViewInfo(view.getClass().getName(), - getContext().getViewKey(view), + getViewKey(view), view.getLeft(), view.getTop(), view.getRight(), view.getBottom(), view, view.getLayoutParams()); } @@ -1495,6 +1501,32 @@ public class RenderSessionImpl extends RenderAction { return result; } + /** + * The cookie for menu items are stored in menu item and not in the map from View stored in + * BridgeContext. + */ + private Object getViewKey(View view) { + BridgeContext context = getContext(); + if (!(view instanceof MenuView.ItemView)) { + return context.getViewKey(view); + } + MenuItemImpl menuItem; + if (view instanceof ActionMenuItemView) { + menuItem = ((ActionMenuItemView) view).getItemData(); + } else if (view instanceof ListMenuItemView) { + menuItem = ((ListMenuItemView) view).getItemData(); + } else if (view instanceof IconMenuItemView) { + menuItem = ((IconMenuItemView) view).getItemData(); + } else { + menuItem = null; + } + if (menuItem instanceof BridgeMenuItemImpl) { + return ((BridgeMenuItemImpl) menuItem).getViewCookie(); + } + + return null; + } + private void invalidateRenderingSize() { mMeasuredScreenWidth = mMeasuredScreenHeight = -1; } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index c03ccb734251..7b0f8f53b28c 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -142,6 +142,8 @@ public final class CreateInfo implements ICreateInfo { "android.view.ViewRootImpl#isInTouchMode", "android.view.WindowManagerGlobal#getWindowManagerService", "android.view.inputmethod.InputMethodManager#getInstance", + "android.view.MenuInflater#registerMenu", + "com.android.internal.view.menu.MenuBuilder#createNewMenuItem", "com.android.internal.util.XmlUtils#convertValueToInt", "com.android.internal.textservice.ITextServicesManager$Stub#asInterface", }; -- 2.11.0