1 /*
2  * Copyright (C) 2015 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 
17 package com.android.layoutlib.bridge.android.support;
18 
19 import com.android.ide.common.rendering.api.LayoutLog;
20 import com.android.ide.common.rendering.api.LayoutlibCallback;
21 import com.android.layoutlib.bridge.Bridge;
22 import com.android.layoutlib.bridge.android.BridgeContext;
23 import com.android.layoutlib.bridge.android.RenderParamsFlags;
24 import com.android.layoutlib.bridge.util.ReflectionUtils;
25 import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
26 
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.content.Context;
30 import android.view.View;
31 
32 import static com.android.layoutlib.bridge.util.ReflectionUtils.getCause;
33 import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
34 import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
35 
36 /**
37  * Utility class for working with android.support.v7.widget.RecyclerView and
38  * androidx.widget.RecyclerView
39  */
40 public class RecyclerViewUtil {
41     public static final String[] CN_RECYCLER_VIEW = {
42             "android.support.v7.widget.RecyclerView",
43             "androidx.recyclerview.widget.RecyclerView"
44     };
45 
46     private static final Class<?>[] LLM_CONSTRUCTOR_SIGNATURE = new Class<?>[]{Context.class};
47 
48     /**
49      * Tries to create an Adapter ({@code android.support.v7.widget.RecyclerView.Adapter} and a
50      * LayoutManager {@code RecyclerView.LayoutManager} and assign these to the {@code RecyclerView}
51      * that is passed.
52      * <p/>
53      * Any exceptions thrown during the process are logged in {@link Bridge#getLog()}
54      */
setAdapter(@onNull View recyclerView, @NonNull BridgeContext context, @NonNull LayoutlibCallback layoutlibCallback, int adapterLayout, int itemCount)55     public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context,
56             @NonNull LayoutlibCallback layoutlibCallback, int adapterLayout, int itemCount) {
57         String recyclerViewClassName =
58                 ReflectionUtils.getParentClass(recyclerView, RecyclerViewUtil.CN_RECYCLER_VIEW);
59         String adapterClassName = recyclerViewClassName + "$Adapter";
60         String layoutMgrClassName = recyclerViewClassName + "$LayoutManager";
61 
62         try {
63             setLayoutManager(recyclerView, layoutMgrClassName, context, layoutlibCallback);
64             Object adapter = createAdapter(layoutlibCallback, adapterClassName);
65             if (adapter != null) {
66                 setProperty(recyclerView, adapterClassName, adapter, "setAdapter");
67                 setProperty(adapter, int.class, adapterLayout, "setLayoutId");
68 
69                 if (itemCount != -1) {
70                     setProperty(adapter, int.class, itemCount, "setItemCount");
71                 }
72             }
73         } catch (ReflectionException e) {
74             Throwable cause = getCause(e);
75             Bridge.getLog().error(LayoutLog.TAG_BROKEN,
76                     "Error occurred while trying to setup RecyclerView.", cause, null);
77         }
78     }
79 
setLayoutManager(@onNull View recyclerView, @NonNull String layoutMgrClassName, @NonNull BridgeContext context, @NonNull LayoutlibCallback callback)80     private static void setLayoutManager(@NonNull View recyclerView,
81             @NonNull String layoutMgrClassName, @NonNull BridgeContext context,
82             @NonNull LayoutlibCallback callback) throws ReflectionException {
83         if (getLayoutManager(recyclerView) == null) {
84             String linearLayoutMgrClassManager =
85                     recyclerView.getClass().getPackage().getName() + ".LinearLayoutManager";
86             // Only set the layout manager if not already set by the recycler view.
87             Object layoutManager =
88                     createLayoutManager(context, linearLayoutMgrClassManager, callback);
89             if (layoutManager != null) {
90                 setProperty(recyclerView, layoutMgrClassName, layoutManager, "setLayoutManager");
91             }
92         }
93     }
94 
95     /** Creates a LinearLayoutManager using the provided context. */
96     @Nullable
createLayoutManager(@onNull Context context, @NonNull String linearLayoutMgrClassName, @NonNull LayoutlibCallback callback)97     private static Object createLayoutManager(@NonNull Context context,
98             @NonNull String linearLayoutMgrClassName, @NonNull LayoutlibCallback callback)
99             throws ReflectionException {
100         try {
101             return callback.loadClass(linearLayoutMgrClassName, LLM_CONSTRUCTOR_SIGNATURE,
102                     new Object[]{context});
103         } catch (Exception e) {
104             throw new ReflectionException(e);
105         }
106     }
107 
108     @Nullable
getLayoutManager(View recyclerView)109     private static Object getLayoutManager(View recyclerView) throws ReflectionException {
110         return invoke(getMethod(recyclerView.getClass(), "getLayoutManager"), recyclerView);
111     }
112 
113     @Nullable
createAdapter(@onNull LayoutlibCallback layoutlibCallback, @NonNull String adapterClassName)114     private static Object createAdapter(@NonNull LayoutlibCallback layoutlibCallback,
115             @NonNull String adapterClassName) throws ReflectionException {
116         Boolean ideSupport =
117                 layoutlibCallback.getFlag(RenderParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT);
118         if (ideSupport != Boolean.TRUE) {
119             return null;
120         }
121         try {
122             return layoutlibCallback.loadClass(adapterClassName, new Class[0], new Object[0]);
123         } catch (Exception e) {
124             throw new ReflectionException(e);
125         }
126     }
127 
setProperty(@onNull Object object, @NonNull String propertyClassName, @NonNull Object propertyValue, @NonNull String propertySetter)128     private static void setProperty(@NonNull Object object, @NonNull String propertyClassName,
129             @NonNull Object propertyValue, @NonNull String propertySetter)
130             throws ReflectionException {
131         Class<?> propertyClass = ReflectionUtils.getClassInstance(propertyValue, propertyClassName);
132         setProperty(object, propertyClass, propertyValue, propertySetter);
133     }
134 
setProperty(@onNull Object object, @NonNull Class<?> propertyClass, @Nullable Object propertyValue, @NonNull String propertySetter)135     private static void setProperty(@NonNull Object object, @NonNull Class<?> propertyClass,
136             @Nullable Object propertyValue, @NonNull String propertySetter)
137             throws ReflectionException {
138         invoke(getMethod(object.getClass(), propertySetter, propertyClass), object, propertyValue);
139     }
140 
141 }
142