diff options
author | Andreas Gampe <agampe@google.com> | 2015-03-12 23:10:53 -0700 |
---|---|---|
committer | Andreas Gampe <agampe@google.com> | 2015-03-12 23:25:27 -0700 |
commit | d88a9bc821a660bd86056e39fe8189d521b0f860 (patch) | |
tree | a2baa728e17e955228b6d9ee58abede7c7f5bbb1 /tools | |
parent | b3fd7e1be658b331cc3e9229aa594fbd1b3d0deb (diff) | |
parent | ef1741d27b19fe63a7d8541b8745e166eb135252 (diff) | |
download | frameworks_base-d88a9bc821a660bd86056e39fe8189d521b0f860.zip frameworks_base-d88a9bc821a660bd86056e39fe8189d521b0f860.tar.gz frameworks_base-d88a9bc821a660bd86056e39fe8189d521b0f860.tar.bz2 |
resolved conflicts for merge of ef1741d2 to master
Change-Id: I5379d5f756695f5176d92249ac6304bffcf95751
Diffstat (limited to 'tools')
6 files changed, 332 insertions, 7 deletions
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index 7e4ff69..fbd5e2a 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -22,9 +22,13 @@ import com.android.ide.common.rendering.api.MergeCookie; import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.layoutlib.bridge.android.support.RecyclerViewUtil; +import com.android.layoutlib.bridge.android.support.RecyclerViewUtil.LayoutManagerType; import com.android.layoutlib.bridge.impl.ParserFactory; +import com.android.layoutlib.bridge.impl.RenderSessionImpl; import com.android.resources.ResourceType; import com.android.util.Pair; @@ -111,8 +115,7 @@ public final class BridgeInflater extends LayoutInflater { } catch (Exception e) { // Wrap the real exception in a ClassNotFoundException, so that the calling method // can deal with it. - ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e); - throw exception; + throw new ClassNotFoundException("onCreateView", e); } setupViewInContext(view, attrs); @@ -123,7 +126,7 @@ public final class BridgeInflater extends LayoutInflater { @Override public View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) { - View view = null; + View view; try { view = super.createViewFromTag(parent, name, attrs, inheritContext); } catch (InflateException e) { @@ -134,7 +137,7 @@ public final class BridgeInflater extends LayoutInflater { // Wrap the real exception in an InflateException so that the calling // method can deal with it. InflateException exception = new InflateException(); - if (e2.getClass().equals(ClassNotFoundException.class) == false) { + if (!e2.getClass().equals(ClassNotFoundException.class)) { exception.initCause(e2); } else { exception.initCause(e); @@ -184,7 +187,7 @@ public final class BridgeInflater extends LayoutInflater { return inflate(bridgeParser, root); } catch (Exception e) { Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, - "Failed to parse file " + f.getAbsolutePath(), e, null /*data*/); + "Failed to parse file " + f.getAbsolutePath(), e, null); return null; } @@ -194,8 +197,7 @@ public final class BridgeInflater extends LayoutInflater { return null; } - private View loadCustomView(String name, AttributeSet attrs) throws ClassNotFoundException, - Exception{ + private View loadCustomView(String name, AttributeSet attrs) throws Exception { if (mProjectCallback != null) { // first get the classname in case it's not the node name if (name.equals("view")) { @@ -227,6 +229,20 @@ public final class BridgeInflater extends LayoutInflater { if (viewKey != null) { bc.addViewKey(view, viewKey); } + if (RenderSessionImpl.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) { + String type = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, + BridgeConstants.ATTR_LAYOUT_MANAGER_TYPE); + if (type != null) { + LayoutManagerType layoutManagerType = LayoutManagerType.getByDisplayName(type); + if (layoutManagerType == null) { + Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED, + "LayoutManager (" + type + ") not found, falling back to " + + "LinearLayoutManager", null); + } else { + bc.addCookie(view, layoutManagerType); + } + } + } } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java index eb9e7f1..fdb4567 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java @@ -48,4 +48,7 @@ public class BridgeConstants { public final static String MATCH_PARENT = "match_parent"; public final static String FILL_PARENT = "fill_parent"; public final static String WRAP_CONTENT = "wrap_content"; + + /** Attribute in the tools namespace used to specify layout manager for RecyclerView. */ + public static final String ATTR_LAYOUT_MANAGER_TYPE = "layoutManagerType"; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java index cfc8f40..2f45473 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java @@ -31,6 +31,8 @@ public final class RenderParamsFlags { new Key<String>("rootTag", String.class); public static final Key<Boolean> FLAG_KEY_DISABLE_BITMAP_CACHING = new Key<Boolean>("disableBitmapCaching", Boolean.class); + public static final Key<Boolean> FLAG_KEY_RECYCLER_VIEW_SUPPORT = + new Key<Boolean>("recyclerViewSupport", Boolean.class); public static final Key<Boolean> FLAG_KEY_RENDER_ALL_DRAWABLE_STATES = new Key<Boolean>("renderAllDrawableStates", Boolean.class); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java new file mode 100644 index 0000000..556f215 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2015 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.layoutlib.bridge.android.support; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.SessionParams; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.RenderParamsFlags; + +import android.content.Context; +import android.view.View; +import android.widget.LinearLayout; + +import java.lang.reflect.Method; +import java.util.HashMap; + +import static com.android.layoutlib.bridge.util.ReflectionUtils.*; + +/** + * Utility class for working with android.support.v7.widget.RecyclerView + */ +@SuppressWarnings("SpellCheckingInspection") // for "recycler". +public class RecyclerViewUtil { + + /** + * Used by {@link LayoutManagerType}. + * <p/> + * Not declared inside the enum, since it needs to be accessible in the constructor. + */ + private static final Object CONTEXT = new Object(); + + public static final String CN_RECYCLER_VIEW = "android.support.v7.widget.RecyclerView"; + private static final String CN_LAYOUT_MANAGER = CN_RECYCLER_VIEW + "$LayoutManager"; + private static final String CN_ADAPTER = CN_RECYCLER_VIEW + "$Adapter"; + + /** + * Tries to create an Adapter ({@code android.support.v7.widget.RecyclerView.Adapter} and a + * LayoutManager {@code RecyclerView.LayoutManager} and assign these to the {@code RecyclerView} + * that is passed. + * <p/> + * Any exceptions thrown during the process are logged in {@link Bridge#getLog()} + */ + public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context, + @NonNull SessionParams params) { + try { + setLayoutManager(recyclerView, context, params.getProjectCallback()); + Object adapter = createAdapter(params); + setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter"); + } catch (ReflectionException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Error occured while trying to setup RecyclerView.", e, null); + } + } + + private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context, + @NonNull IProjectCallback callback) throws ReflectionException { + Object cookie = context.getCookie(recyclerView); + assert cookie == null || cookie instanceof LayoutManagerType; + if (cookie == null) { + cookie = LayoutManagerType.getDefault(); + } + Object layoutManager = createLayoutManager((LayoutManagerType) cookie, context, callback); + setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager"); + } + + @Nullable + private static Object createLayoutManager(@Nullable LayoutManagerType type, + @NonNull Context context, @NonNull IProjectCallback callback) + throws ReflectionException { + if (type == null) { + type = LayoutManagerType.getDefault(); + } + try { + return callback.loadView(type.getClassName(), type.getSignature(), type.getArgs(context)); + } catch (Exception e) { + throw new ReflectionException(e); + } + } + + @Nullable + private static Object createAdapter(@NonNull SessionParams params) throws ReflectionException { + Boolean ideSupport = params.getFlag(RenderParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT); + if (ideSupport != Boolean.TRUE) { + return null; + } + try { + return params.getProjectCallback().loadView(CN_ADAPTER, new Class[0], new Object[0]); + } catch (Exception e) { + throw new ReflectionException(e); + } + } + + private static void setProperty(@NonNull View recyclerView, @NonNull String propertyClassName, + @Nullable Object propertyValue, @NonNull String propertySetter) + throws ReflectionException { + if (propertyValue != null) { + Class<?> layoutManagerClass = getClassInstance(propertyValue, propertyClassName); + Method setLayoutManager = getMethod(recyclerView.getClass(), + propertySetter, layoutManagerClass); + if (setLayoutManager != null) { + invoke(setLayoutManager, recyclerView, propertyValue); + } + } + } + + /** + * Looks through the class hierarchy of {@code object} at runtime and returns the class matching + * the name {@code className}. + * <p/> + * This is used when we cannot use Class.forName() since the class we want was loaded from a + * different ClassLoader. + */ + @NonNull + private static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) { + Class<?> superClass = object.getClass(); + while (superClass != null) { + if (className.equals(superClass.getName())) { + return superClass; + } + superClass = superClass.getSuperclass(); + } + throw new RuntimeException("invalid object/classname combination."); + } + + /** Supported LayoutManagers. */ + public enum LayoutManagerType { + LINEAR_LAYOUT_MANGER("Linear", + "android.support.v7.widget.LinearLayoutManager", + new Class[]{Context.class}, new Object[]{CONTEXT}), + GRID_LAYOUT_MANAGER("Grid", + "android.support.v7.widget.GridLayoutManager", + new Class[]{Context.class, int.class}, new Object[]{CONTEXT, 2}), + STAGGERED_GRID_LAYOUT_MANAGER("StaggeredGrid", + "android.support.v7.widget.StaggeredGridLayoutManager", + new Class[]{int.class, int.class}, new Object[]{2, LinearLayout.VERTICAL}); + + private String mDisplayName; + private String mClassName; + private Class[] mSignature; + private Object[] mArgs; + + private static final HashMap<String, LayoutManagerType> sDisplayNameLookup = + new HashMap<String, LayoutManagerType>(); + + static { + for (LayoutManagerType type : LayoutManagerType.values()) { + sDisplayNameLookup.put(type.mDisplayName, type); + } + } + + LayoutManagerType(String displayName, String className, Class[] signature, Object[] args) { + mDisplayName = displayName; + mClassName = className; + mSignature = signature; + mArgs = args; + } + + String getClassName() { + return mClassName; + } + + Class[] getSignature() { + return mSignature; + } + + @NonNull + Object[] getArgs(Context context) { + Object[] args = new Object[mArgs.length]; + System.arraycopy(mArgs, 0, args, 0, mArgs.length); + for (int i = 0; i < args.length; i++) { + if (args[i] == CONTEXT) { + args[i] = context; + } + } + return args; + } + + @NonNull + public static LayoutManagerType getDefault() { + return LINEAR_LAYOUT_MANGER; + } + + @Nullable + public static LayoutManagerType getByDisplayName(@Nullable String className) { + return sDisplayNameLookup.get(className); + } + } +} 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 607563a..c9aa400 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 @@ -23,6 +23,8 @@ import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN; import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.ide.common.rendering.api.AdapterBinding; import com.android.ide.common.rendering.api.HardwareConfig; import com.android.ide.common.rendering.api.IAnimationListener; @@ -51,6 +53,7 @@ import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.android.RenderParamsFlags; +import com.android.layoutlib.bridge.android.support.RecyclerViewUtil; import com.android.layoutlib.bridge.bars.BridgeActionBar; import com.android.layoutlib.bridge.bars.AppCompatActionBar; import com.android.layoutlib.bridge.bars.Config; @@ -1338,6 +1341,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } } } + } else if (isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) { + RecyclerViewUtil.setAdapter(view, getContext(), getParams()); } else if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; final int count = group.getChildCount(); @@ -1349,6 +1354,22 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } /** + * Check if the object is an instance of a class named {@code className}. This doesn't work + * for interfaces. + */ + public static boolean isInstanceOf(Object object, String className) { + Class superClass = object.getClass(); + while (superClass != null) { + String name = superClass.getName(); + if (name.equals(className)) { + return true; + } + superClass = superClass.getSuperclass(); + } + return false; + } + + /** * Sets up a {@link TabHost} object. * @param tabHost the TabHost to setup. * @param projectCallback The project callback object to access the project R class. @@ -1505,6 +1526,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at * index 1 is with the offset. */ + @NonNull private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) { ViewInfo[] result = new ViewInfo[2]; if (view == null) { @@ -1600,6 +1622,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * The cookie for menu items are stored in menu item and not in the map from View stored in * BridgeContext. */ + @Nullable private Object getViewKey(View view) { BridgeContext context = getContext(); if (!(view instanceof MenuView.ItemView)) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java new file mode 100644 index 0000000..8e61edf --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2015 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.layoutlib.bridge.util; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Utility to convert checked Reflection exceptions to unchecked exceptions. + */ +public class ReflectionUtils { + + @Nullable + public static Method getMethod(@NonNull Class<?> clazz, @NonNull String name, + @Nullable Class<?>... params) throws ReflectionException { + try { + return clazz.getMethod(name, params); + } catch (NoSuchMethodException e) { + throw new ReflectionException(e); + } + } + + @Nullable + public static Object invoke(@NonNull Method method, @Nullable Object object, + @Nullable Object... args) throws ReflectionException { + Exception ex; + try { + return method.invoke(object, args); + } catch (IllegalAccessException e) { + ex = e; + } catch (InvocationTargetException e) { + ex = e; + } + throw new ReflectionException(ex); + } + + /** + * Wraps all reflection related exceptions. Created since ReflectiveOperationException was + * introduced in 1.7 and we are still on 1.6 + */ + public static class ReflectionException extends Exception { + public ReflectionException() { + super(); + } + + public ReflectionException(String message) { + super(message); + } + + public ReflectionException(String message, Throwable cause) { + super(message, cause); + } + + public ReflectionException(Throwable cause) { + super(cause); + } + } +} |