1d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta/*
2d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta * Copyright (C) 2015 The Android Open Source Project
3d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta *
4d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta * Licensed under the Apache License, Version 2.0 (the "License");
5d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta * you may not use this file except in compliance with the License.
6d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta * You may obtain a copy of the License at
7d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta *
8d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta *      http://www.apache.org/licenses/LICENSE-2.0
9d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta *
10d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta * Unless required by applicable law or agreed to in writing, software
11d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta * distributed under the License is distributed on an "AS IS" BASIS,
12d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta * See the License for the specific language governing permissions and
14d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta * limitations under the License.
15d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta */
16d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
17d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Guptapackage com.android.layoutlib.bridge.android.support;
18d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
19d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Guptaimport com.android.ide.common.rendering.api.LayoutLog;
2037dbb8b7f3c069196040eed3a03006647db7fa5bDeepanshu Guptaimport com.android.ide.common.rendering.api.LayoutlibCallback;
21d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Guptaimport com.android.layoutlib.bridge.Bridge;
22d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Guptaimport com.android.layoutlib.bridge.android.BridgeContext;
232bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Guptaimport com.android.layoutlib.bridge.android.RenderParamsFlags;
24671b7f9b99316f7224c1213d6923d449a2de9b62Deepanshu Guptaimport com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
25d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
26476e582d2ffdf25102d4c55f8c242baa3d21d37fDeepanshu Guptaimport android.annotation.NonNull;
27476e582d2ffdf25102d4c55f8c242baa3d21d37fDeepanshu Guptaimport android.annotation.Nullable;
28d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Guptaimport android.content.Context;
29d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Guptaimport android.view.View;
30d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
31ccbc11770397888cf7780925bb4c7cf1d2f2f80eDeepanshu Guptaimport static com.android.layoutlib.bridge.util.ReflectionUtils.getCause;
322bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Guptaimport static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
332bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Guptaimport static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
34d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
35d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta/**
36d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta * Utility class for working with android.support.v7.widget.RecyclerView
37d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta */
38d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Guptapublic class RecyclerViewUtil {
39d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
402bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta    private static final String RV_PKG_PREFIX = "android.support.v7.widget.";
412bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta    public static final String CN_RECYCLER_VIEW = RV_PKG_PREFIX + "RecyclerView";
42d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    private static final String CN_LAYOUT_MANAGER = CN_RECYCLER_VIEW + "$LayoutManager";
43d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    private static final String CN_ADAPTER = CN_RECYCLER_VIEW + "$Adapter";
44d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
452bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta    // LinearLayoutManager related constants.
462bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta    private static final String CN_LINEAR_LAYOUT_MANAGER = RV_PKG_PREFIX + "LinearLayoutManager";
472bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta    private static final Class<?>[] LLM_CONSTRUCTOR_SIGNATURE = new Class<?>[]{Context.class};
482bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta
49d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    /**
50d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     * Tries to create an Adapter ({@code android.support.v7.widget.RecyclerView.Adapter} and a
51d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     * LayoutManager {@code RecyclerView.LayoutManager} and assign these to the {@code RecyclerView}
52d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     * that is passed.
53d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     * <p/>
54d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     * Any exceptions thrown during the process are logged in {@link Bridge#getLog()}
55d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     */
56d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context,
5761f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta            @NonNull LayoutlibCallback layoutlibCallback, int adapterLayout) {
58d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        try {
5961f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta            setLayoutManager(recyclerView, context, layoutlibCallback);
6061f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta            Object adapter = createAdapter(layoutlibCallback);
6161f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta            if (adapter != null) {
6261f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta                setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter");
6361f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta                setProperty(adapter, int.class, adapterLayout, "setLayoutId");
6461f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta            }
65d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        } catch (ReflectionException e) {
6661f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta            Throwable cause = getCause(e);
67d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta            Bridge.getLog().error(LayoutLog.TAG_BROKEN,
6861f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta                    "Error occurred while trying to setup RecyclerView.", cause, null);
69d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        }
70d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    }
71d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
72d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context,
7337dbb8b7f3c069196040eed3a03006647db7fa5bDeepanshu Gupta            @NonNull LayoutlibCallback callback) throws ReflectionException {
742bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta        if (getLayoutManager(recyclerView) == null) {
752bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta            // Only set the layout manager if not already set by the recycler view.
762bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta            Object layoutManager = createLayoutManager(context, callback);
7761f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta            if (layoutManager != null) {
7861f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta                setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager");
7961f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta            }
80d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        }
81d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    }
82d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
832bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta    /** Creates a LinearLayoutManager using the provided context. */
84d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    @Nullable
852bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta    private static Object createLayoutManager(@NonNull Context context,
862bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta            @NonNull LayoutlibCallback callback)
87d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta            throws ReflectionException {
88d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        try {
892bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta            return callback.loadView(CN_LINEAR_LAYOUT_MANAGER, LLM_CONSTRUCTOR_SIGNATURE,
9061f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta                    new Object[]{context});
91d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        } catch (Exception e) {
92d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta            throw new ReflectionException(e);
93d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        }
94d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    }
95d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
96d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    @Nullable
9761f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta    private static Object getLayoutManager(View recyclerView) throws ReflectionException {
98671b7f9b99316f7224c1213d6923d449a2de9b62Deepanshu Gupta        return invoke(getMethod(recyclerView.getClass(), "getLayoutManager"), recyclerView);
992bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta    }
1002bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta
1012bc2daa74eef01135f717eadfab87538a9bef29fDeepanshu Gupta    @Nullable
10261f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta    private static Object createAdapter(@NonNull LayoutlibCallback layoutlibCallback)
10361f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta            throws ReflectionException {
10461f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta        Boolean ideSupport =
10561f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta                layoutlibCallback.getFlag(RenderParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT);
106d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        if (ideSupport != Boolean.TRUE) {
107d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta            return null;
108d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        }
109d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        try {
11061f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta            return layoutlibCallback.loadClass(CN_ADAPTER, new Class[0], new Object[0]);
111d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        } catch (Exception e) {
112d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta            throw new ReflectionException(e);
113d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        }
114d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    }
115d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
11661f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta    private static void setProperty(@NonNull Object object, @NonNull String propertyClassName,
11761f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta      @NonNull Object propertyValue, @NonNull String propertySetter)
11861f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta            throws ReflectionException {
11961f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta        Class<?> propertyClass = getClassInstance(propertyValue, propertyClassName);
12061f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta        setProperty(object, propertyClass, propertyValue, propertySetter);
12161f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta    }
12261f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta
12361f23e9bf7d784e7a52168196758c4f6c6853e77Deepanshu Gupta    private static void setProperty(@NonNull Object object, @NonNull Class<?> propertyClass,
124d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta            @Nullable Object propertyValue, @NonNull String propertySetter)
125d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta            throws ReflectionException {
126671b7f9b99316f7224c1213d6923d449a2de9b62Deepanshu Gupta        invoke(getMethod(object.getClass(), propertySetter, propertyClass), object, propertyValue);
127d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    }
128d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta
129d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    /**
130d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     * Looks through the class hierarchy of {@code object} at runtime and returns the class matching
131d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     * the name {@code className}.
132d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     * <p/>
133d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     * This is used when we cannot use Class.forName() since the class we want was loaded from a
134d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     * different ClassLoader.
135d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta     */
136d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    @NonNull
137d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    private static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) {
138d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        Class<?> superClass = object.getClass();
139d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        while (superClass != null) {
140d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta            if (className.equals(superClass.getName())) {
141d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta                return superClass;
142d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta            }
143d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta            superClass = superClass.getSuperclass();
144d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        }
145d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta        throw new RuntimeException("invalid object/classname combination.");
146d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta    }
147d345f44a87de1088fcd19e021238852bbffbbaecDeepanshu Gupta}
148