Merge "Add drawable caching"
diff --git a/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
index 1dfd305..2903e3a 100644
--- a/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
+++ b/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -91,16 +91,14 @@
 
     @LayoutlibDelegate
     static long nCreateTree(long rootGroupPtr) {
-        VGroup_Delegate rootGroup = VNativeObject.getDelegate(rootGroupPtr);
-        return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rootGroup));
+        return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rootGroupPtr));
     }
 
     @LayoutlibDelegate
     static long nCreateTreeFromCopy(long rendererToCopyPtr, long rootGroupPtr) {
-        VGroup_Delegate rootGroup = VNativeObject.getDelegate(rootGroupPtr);
         VPathRenderer_Delegate rendererToCopy = VNativeObject.getDelegate(rendererToCopyPtr);
         return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rendererToCopy,
-                rootGroup));
+                rootGroupPtr));
     }
 
     @LayoutlibDelegate
@@ -163,7 +161,6 @@
     @LayoutlibDelegate
     static long nCreateFullPath(long nativeFullPathPtr) {
         VFullPath_Delegate original = VNativeObject.getDelegate(nativeFullPathPtr);
-
         return sPathManager.addNewDelegate(new VFullPath_Delegate(original));
     }
 
@@ -247,8 +244,7 @@
     @LayoutlibDelegate
     static long nCreateGroup(long groupPtr) {
         VGroup_Delegate original = VNativeObject.getDelegate(groupPtr);
-        return sPathManager.addNewDelegate(
-                new VGroup_Delegate(original, new ArrayMap<String, Object>()));
+        return sPathManager.addNewDelegate(new VGroup_Delegate(original, new ArrayMap<>()));
     }
 
     @LayoutlibDelegate
@@ -1029,7 +1025,7 @@
         private final Path mPath;
         private final Path mRenderPath;
         private final Matrix mFinalPathMatrix = new Matrix();
-        private final VGroup_Delegate mRootGroup;
+        private final long mRootGroupPtr;
         private float mViewportWidth = 0;
         private float mViewportHeight = 0;
         private float mRootAlpha = 1.0f;
@@ -1037,15 +1033,15 @@
         private Paint mFillPaint;
         private PathMeasure mPathMeasure;
 
-        private VPathRenderer_Delegate(VGroup_Delegate rootGroup) {
-            mRootGroup = rootGroup;
+        private VPathRenderer_Delegate(long rootGroupPtr) {
+            mRootGroupPtr = rootGroupPtr;
             mPath = new Path();
             mRenderPath = new Path();
         }
 
         private VPathRenderer_Delegate(VPathRenderer_Delegate rendererToCopy,
-                VGroup_Delegate rootGroup) {
-            this(rootGroup);
+                long rootGroupPtr) {
+            this(rootGroupPtr);
             mViewportWidth = rendererToCopy.mViewportWidth;
             mViewportHeight = rendererToCopy.mViewportHeight;
             mRootAlpha = rendererToCopy.mRootAlpha;
@@ -1087,7 +1083,7 @@
 
         public void draw(long canvasPtr, long filterPtr, int w, int h) {
             // Traverse the tree in pre-order to draw.
-            drawGroupTree(mRootGroup, Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr);
+            drawGroupTree(VNativeObject.getDelegate(mRootGroupPtr), Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr);
         }
 
         private void drawPath(VGroup_Delegate VGroup, VPath_Delegate VPath, long canvasPtr,
@@ -1227,5 +1223,14 @@
         @Override
         public void setName(String name) {
         }
+
+        @Override
+        protected void finalize() throws Throwable {
+            super.finalize();
+
+            // The mRootGroupPtr is not explicitly freed by anything in the VectorDrawable so we
+            // need to free it here.
+            sPathManager.removeJavaReferenceFor(mRootGroupPtr);
+        }
     }
 }
diff --git a/bridge/src/android/preference/BridgePreferenceInflater.java b/bridge/src/android/preference/BridgePreferenceInflater.java
index 4f00b5d..aa393a9 100644
--- a/bridge/src/android/preference/BridgePreferenceInflater.java
+++ b/bridge/src/android/preference/BridgePreferenceInflater.java
@@ -21,6 +21,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.InflateException;
 
 public class BridgePreferenceInflater extends PreferenceInflater {
 
@@ -42,7 +43,15 @@
             viewKey = ((BridgeXmlBlockParser) attrs).getViewCookie();
         }
 
-        Preference preference = super.onCreateItem(name, attrs);
+        Preference preference = null;
+        try {
+            preference = super.onCreateItem(name, attrs);
+        } catch (ClassNotFoundException | InflateException exception) {
+            // name is probably not a valid preference type
+            if ("SwitchPreferenceCompat".equals(name)) {
+                preference = super.onCreateItem("SwitchPreference", attrs);
+            }
+        }
 
         if (viewKey != null && bc != null) {
             bc.addCookie(preference, viewKey);
diff --git a/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java b/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
index d432120..ab27819 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
@@ -21,6 +21,7 @@
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.util.ReflectionUtils;
 import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
 
 import android.annotation.NonNull;
@@ -116,7 +117,7 @@
     private static void setProperty(@NonNull Object object, @NonNull String propertyClassName,
       @NonNull Object propertyValue, @NonNull String propertySetter)
             throws ReflectionException {
-        Class<?> propertyClass = getClassInstance(propertyValue, propertyClassName);
+        Class<?> propertyClass = ReflectionUtils.getClassInstance(propertyValue, propertyClassName);
         setProperty(object, propertyClass, propertyValue, propertySetter);
     }
 
@@ -126,22 +127,4 @@
         invoke(getMethod(object.getClass(), propertySetter, propertyClass), object, 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.");
-    }
 }
diff --git a/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java b/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java
new file mode 100644
index 0000000..0124e83
--- /dev/null
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2016 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.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ScrollView;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getClassInstance;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
+
+/**
+ * Class with utility methods to instantiate Preferences provided by the support library.
+ * This class uses reflection to access the support preference objects so it heavily depends on
+ * the API being stable.
+ */
+public class SupportPreferencesUtil {
+    private static final String PREFERENCE_PKG = "android.support.v7.preference";
+    private static final String PREFERENCE_MANAGER = PREFERENCE_PKG + ".PreferenceManager";
+    private static final String PREFERENCE_GROUP = PREFERENCE_PKG + ".PreferenceGroup";
+    private static final String PREFERENCE_GROUP_ADAPTER =
+      PREFERENCE_PKG + ".PreferenceGroupAdapter";
+    private static final String PREFERENCE_INFLATER = PREFERENCE_PKG + ".PreferenceInflater";
+
+    private SupportPreferencesUtil() {
+    }
+
+    @NonNull
+    private static Object instantiateClass(@NonNull LayoutlibCallback callback,
+            @NonNull String className, @Nullable Class[] constructorSignature,
+            @Nullable Object[] constructorArgs) throws ReflectionException {
+        try {
+            Object instance = callback.loadClass(className, constructorSignature, constructorArgs);
+            if (instance == null) {
+                throw new ClassNotFoundException(className + " class not found");
+            }
+            return instance;
+        } catch (ClassNotFoundException e) {
+            throw new ReflectionException(e);
+        }
+    }
+
+    @NonNull
+    private static Object createPreferenceGroupAdapter(@NonNull LayoutlibCallback callback,
+            @NonNull Object preferenceScreen) throws ReflectionException {
+        Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP);
+
+        return instantiateClass(callback, PREFERENCE_GROUP_ADAPTER,
+                new Class[]{preferenceGroupClass}, new Object[]{preferenceScreen});
+    }
+
+    @NonNull
+    private static Object createInflatedPreference(@NonNull LayoutlibCallback callback,
+      @NonNull Context context, @NonNull XmlPullParser parser, @NonNull Object preferenceScreen,
+      @NonNull Object preferenceManager) throws ReflectionException {
+        Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP);
+        Object preferenceInflater = instantiateClass(callback, PREFERENCE_INFLATER,
+          new Class[]{Context.class, preferenceManager.getClass()},
+          new Object[]{context, preferenceManager});
+        Object inflatedPreference = invoke(
+          getMethod(preferenceInflater.getClass(), "inflate", XmlPullParser.class,
+            preferenceGroupClass), preferenceInflater, parser, null);
+
+        if (inflatedPreference == null) {
+            throw new ReflectionException("inflate method returned null");
+        }
+
+        return inflatedPreference;
+    }
+
+    /**
+     * Returns a themed wrapper context of {@link BridgeContext} with the theme specified in
+     * ?attr/preferenceTheme applied to it.
+     */
+    @Nullable
+    private static Context getThemedContext(@NonNull BridgeContext bridgeContext) {
+        RenderResources resources = bridgeContext.getRenderResources();
+        ResourceValue preferenceTheme = resources.findItemInTheme("preferenceTheme", false);
+
+        if (preferenceTheme != null) {
+            // resolve it, if needed.
+            preferenceTheme = resources.resolveResValue(preferenceTheme);
+        }
+        if (preferenceTheme instanceof StyleResourceValue) {
+            int styleId = bridgeContext.getDynamicIdByStyle(((StyleResourceValue) preferenceTheme));
+            if (styleId != 0) {
+                return new ContextThemeWrapper(bridgeContext, styleId);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns a {@link LinearLayout} containing all the UI widgets representing the preferences
+     * passed in the group adapter.
+     */
+    @Nullable
+    private static LinearLayout setUpPreferencesListView(@NonNull BridgeContext bridgeContext,
+            @NonNull Context themedContext, @NonNull ArrayList<Object> viewCookie,
+            @NonNull Object preferenceGroupAdapter) throws ReflectionException {
+        // Setup the LinearLayout that will contain the preferences
+        LinearLayout listView = new LinearLayout(themedContext);
+        listView.setOrientation(LinearLayout.VERTICAL);
+        listView.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+
+        if (!viewCookie.isEmpty()) {
+            bridgeContext.addViewKey(listView, viewCookie.get(0));
+        }
+
+        // Get all the preferences and add them to the LinearLayout
+        Integer preferencesCount =
+                (Integer) invoke(getMethod(preferenceGroupAdapter.getClass(), "getItemCount"),
+                        preferenceGroupAdapter);
+        if (preferencesCount == null) {
+            return listView;
+        }
+
+        Method getItemId = getMethod(preferenceGroupAdapter.getClass(), "getItemId", int.class);
+        Method getItemViewType =
+                getMethod(preferenceGroupAdapter.getClass(), "getItemViewType", int.class);
+        Method onCreateViewHolder =
+                getMethod(preferenceGroupAdapter.getClass(), "onCreateViewHolder", ViewGroup.class,
+                        int.class);
+        for (int i = 0; i < preferencesCount; i++) {
+            Long id = (Long) invoke(getItemId, preferenceGroupAdapter, i);
+            if (id == null) {
+                continue;
+            }
+
+            // Get the type of the preference layout and bind it to a newly created view holder
+            Integer type = (Integer) invoke(getItemViewType, preferenceGroupAdapter, i);
+            Object viewHolder =
+                    invoke(onCreateViewHolder, preferenceGroupAdapter, listView, type);
+            if (viewHolder == null) {
+                continue;
+            }
+            invoke(getMethod(preferenceGroupAdapter.getClass(), "onBindViewHolder",
+                    viewHolder.getClass(), int.class), preferenceGroupAdapter, viewHolder, i);
+
+            try {
+                // Get the view from the view holder and add it to our layout
+                View itemView =
+                        (View) viewHolder.getClass().getField("itemView").get(viewHolder);
+
+                int arrayPosition = id.intValue() - 1; // IDs are 1 based
+                if (arrayPosition >= 0 && arrayPosition < viewCookie.size()) {
+                    bridgeContext.addViewKey(itemView, viewCookie.get(arrayPosition));
+                }
+                listView.addView(itemView);
+            } catch (IllegalAccessException | NoSuchFieldException ignored) {
+            }
+        }
+
+        return listView;
+    }
+
+    /**
+     * Inflates a preferences layout using the support library. If the support library is not
+     * available, this method will return null without advancing the parsers.
+     */
+    @Nullable
+    public static View inflatePreference(@NonNull BridgeContext bridgeContext,
+            @NonNull XmlPullParser parser, @Nullable ViewGroup root) {
+        try {
+            LayoutlibCallback callback = bridgeContext.getLayoutlibCallback();
+
+            Context context = getThemedContext(bridgeContext);
+            if (context == null) {
+                // Probably we couldn't find the "preferenceTheme" in the theme
+                return null;
+            }
+
+            // Create PreferenceManager
+            Object preferenceManager =
+                    instantiateClass(callback, PREFERENCE_MANAGER, new Class[]{Context.class},
+                            new Object[]{context});
+
+            // From this moment on, we can assume that we found the support library and that
+            // nothing should fail
+
+            // Create PreferenceScreen
+            Object preferenceScreen =
+                    invoke(getMethod(preferenceManager.getClass(), "createPreferenceScreen",
+                            Context.class), preferenceManager, context);
+            if (preferenceScreen == null) {
+                return null;
+            }
+
+            // Setup a parser that stores the list of cookies in the same order as the preferences
+            // are inflated. That way we can later reconstruct the list using the preference id
+            // since they are sequential and start in 1.
+            ArrayList<Object> viewCookie = new ArrayList<>();
+            if (parser instanceof BridgeXmlBlockParser) {
+                // Setup a parser that stores the XmlTag
+                parser = new BridgeXmlBlockParser(parser, null, false) {
+                    @Override
+                    public Object getViewCookie() {
+                        return ((BridgeXmlBlockParser) getParser()).getViewCookie();
+                    }
+
+                    @Override
+                    public int next() throws XmlPullParserException, IOException {
+                        int ev = super.next();
+                        if (ev == XmlPullParser.START_TAG) {
+                            viewCookie.add(this.getViewCookie());
+                        }
+
+                        return ev;
+                    }
+                };
+            }
+
+            // Create the PreferenceInflater
+            Object inflatedPreference =
+              createInflatedPreference(callback, context, parser, preferenceScreen,
+                preferenceManager);
+
+            // Setup the RecyclerView (set adapter and layout manager)
+            Object preferenceGroupAdapter =
+                    createPreferenceGroupAdapter(callback, inflatedPreference);
+
+            // Instead of just setting the group adapter as adapter for a RecyclerView, we manually
+            // get all the items and add them to a LinearLayout. This allows us to set the view
+            // cookies so the preferences are correctly linked to their XML.
+            LinearLayout listView = setUpPreferencesListView(bridgeContext, context, viewCookie,
+                    preferenceGroupAdapter);
+
+            ScrollView scrollView = new ScrollView(context);
+            scrollView.setLayoutParams(
+              new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+            scrollView.addView(listView);
+
+            if (root != null) {
+                root.addView(scrollView);
+            }
+
+            return scrollView;
+        } catch (ReflectionException e) {
+            return null;
+        }
+    }
+}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java b/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
index 0c39026..ea40ba7 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
@@ -165,5 +165,6 @@
             int idx = sDelegates.indexOfValue(reference);
             out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName());
         }
+        out.printf("\nTotal number of objects: %d\n", sJavaReferences.size());
     }
 }
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index c890793..feed045 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -45,6 +45,7 @@
 import com.android.layoutlib.bridge.android.RenderParamsFlags;
 import com.android.layoutlib.bridge.android.graphics.NopCanvas;
 import com.android.layoutlib.bridge.android.support.DesignLibUtil;
+import com.android.layoutlib.bridge.android.support.SupportPreferencesUtil;
 import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
 import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
 import com.android.resources.ResourceType;
@@ -326,8 +327,14 @@
             boolean isPreference = "PreferenceScreen".equals(rootTag);
             View view;
             if (isPreference) {
-                view = Preference_Delegate.inflatePreference(getContext(), mBlockParser,
+                // First try to use the support library inflater. If something fails, fallback
+                // to the system preference inflater.
+                view = SupportPreferencesUtil.inflatePreference(getContext(), mBlockParser,
                         mContentRoot);
+                if (view == null) {
+                    view = Preference_Delegate.inflatePreference(getContext(), mBlockParser,
+                            mContentRoot);
+                }
             } else {
                 view = mInflater.inflate(mBlockParser, mContentRoot);
             }
diff --git a/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java b/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
index 7ce27b6..040191e 100644
--- a/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
+++ b/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
@@ -37,6 +37,15 @@
         }
     }
 
+    @NonNull
+    public static Method getAccessibleMethod(@NonNull Class<?> clazz, @NonNull String name,
+      @Nullable Class<?>... params) throws ReflectionException {
+        Method method = getMethod(clazz, name, params);
+        method.setAccessible(true);
+
+        return method;
+    }
+
     @Nullable
     public static Object invoke(@NonNull Method method, @Nullable Object object,
             @Nullable Object... args) throws ReflectionException {
@@ -74,6 +83,25 @@
     }
 
     /**
+     * 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
+    public 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.");
+    }
+
+    /**
      * Wraps all reflection related exceptions. Created since ReflectiveOperationException was
      * introduced in 1.7 and we are still on 1.6
      */
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index ba687fe..d160268 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -349,7 +349,11 @@
         obj = null;
         while(ref.get() != null) {
             System.gc();
+            System.runFinalization();
         }
+
+        System.gc();
+        System.runFinalization();
     }
 
     @AfterClass