Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d88a9bc8 authored by Andreas Gampe's avatar Andreas Gampe
Browse files

resolved conflicts for merge of ef1741d2 to master

Change-Id: I5379d5f756695f5176d92249ac6304bffcf95751
parents b3fd7e1b ef1741d2
Loading
Loading
Loading
Loading
+23 −7
Original line number Diff line number Diff line
@@ -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);
                    }
                }
            }
        }
    }

+3 −0
Original line number Diff line number Diff line
@@ -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";
}
+2 −0
Original line number Diff line number Diff line
@@ -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);

+206 −0
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}
+23 −0
Original line number Diff line number Diff line
@@ -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();
@@ -1348,6 +1353,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.
@@ -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)) {
Loading