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.internal.widget.RecyclerView;
22 import com.android.layoutlib.bridge.Bridge;
23 import com.android.layoutlib.bridge.android.BridgeContext;
24 import com.android.layoutlib.bridge.android.RenderParamsFlags;
25 import com.android.layoutlib.bridge.util.ReflectionUtils;
26 import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
27 
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.content.Context;
31 import android.view.View;
32 
33 import static com.android.layoutlib.bridge.util.ReflectionUtils.getCause;
34 import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
35 import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
36 
37 /**
38  * Utility class for working with android.support.v7.widget.RecyclerView and
39  * androidx.widget.RecyclerView
40  */
41 public class RecyclerViewUtil {
42     public static final String[] CN_RECYCLER_VIEW = {
43             "android.support.v7.widget.RecyclerView",
44             "androidx.widget.RecyclerView"
45     };
46 
47     private static final Class<?>[] LLM_CONSTRUCTOR_SIGNATURE = new Class<?>[]{Context.class};
48 
49     /**
50      * Tries to create an Adapter ({@code android.support.v7.widget.RecyclerView.Adapter} and a
51      * LayoutManager {@code RecyclerView.LayoutManager} and assign these to the {@code RecyclerView}
52      * that is passed.
53      * <p/>
54      * Any exceptions thrown during the process are logged in {@link Bridge#getLog()}
55      */
setAdapter(@onNull View recyclerView, @NonNull BridgeContext context, @NonNull LayoutlibCallback layoutlibCallback, int adapterLayout, int itemCount)56     public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context,
57             @NonNull LayoutlibCallback layoutlibCallback, int adapterLayout, int itemCount) {
58         String recyclerViewClassName = recyclerView.getClass().getName();
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.loadView(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 layoutMgrClassName)114     private static Object createAdapter(@NonNull LayoutlibCallback layoutlibCallback,
115             @NonNull String layoutMgrClassName) 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(layoutMgrClassName, 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