Merge "[RenderScript] Make support lib harder to leak context."
diff --git a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
index 383717e..ed3101b 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
@@ -45,9 +45,11 @@
 
     int mSpanCount = DEFAULT_SPAN_COUNT;
     /**
-     * The size of each span
+     * Right borders for each span.
+     * <p>For <b>i-th</b> item start is {@link #mCachedBorders}[i-1] + 1
+     * and end is {@link #mCachedBorders}[i].
      */
-    int mSizePerSpan;
+    int [] mCachedBorders;
     /**
      * Temporary array to keep views in layoutChunk method
      */
@@ -246,7 +248,29 @@
         } else {
             totalSpace = getHeight() - getPaddingBottom() - getPaddingTop();
         }
-        mSizePerSpan = totalSpace / mSpanCount;
+        calculateItemBorders(totalSpace);
+    }
+
+    private void calculateItemBorders(int totalSpace) {
+        if (mCachedBorders == null || mCachedBorders.length != mSpanCount + 1
+                || mCachedBorders[mCachedBorders.length - 1] != totalSpace) {
+            mCachedBorders = new int[mSpanCount + 1];
+        }
+        mCachedBorders[0] = 0;
+        int sizePerSpan = totalSpace / mSpanCount;
+        int sizePerSpanRemainder = totalSpace % mSpanCount;
+        int consumedPixels = 0;
+        int additionalSize = 0;
+        for (int i = 1; i <= mSpanCount; i++) {
+            int itemSize = sizePerSpan;
+            additionalSize += sizePerSpanRemainder;
+            if (additionalSize > 0 && (mSpanCount - additionalSize) < sizePerSpanRemainder) {
+                itemSize += 1;
+                additionalSize -= mSpanCount;
+            }
+            consumedPixels += itemSize;
+            mCachedBorders[i] = consumedPixels;
+        }
     }
 
     @Override
@@ -387,10 +411,11 @@
                 }
             }
 
-            int spanSize = getSpanSize(recycler, state, getPosition(view));
-            final int spec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan * spanSize,
-                    View.MeasureSpec.EXACTLY);
             final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            final int spec = View.MeasureSpec.makeMeasureSpec(
+                    mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
+                            mCachedBorders[lp.mSpanIndex],
+                    View.MeasureSpec.EXACTLY);
             if (mOrientation == VERTICAL) {
                 measureChildWithDecorationsAndMargin(view, spec, getMainDirSpec(lp.height));
             } else {
@@ -407,8 +432,10 @@
         for (int i = 0; i < count; i ++) {
             final View view = mSet[i];
             if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
-                int spanSize = getSpanSize(recycler, state, getPosition(view));
-                final int spec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan * spanSize,
+                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                final int spec = View.MeasureSpec.makeMeasureSpec(
+                        mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
+                                mCachedBorders[lp.mSpanIndex],
                         View.MeasureSpec.EXACTLY);
                 if (mOrientation == VERTICAL) {
                     measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec);
@@ -442,10 +469,10 @@
             View view = mSet[i];
             LayoutParams params = (LayoutParams) view.getLayoutParams();
             if (mOrientation == VERTICAL) {
-                left = getPaddingLeft() + mSizePerSpan * params.mSpanIndex;
+                left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
                 right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
             } else {
-                top = getPaddingTop() + mSizePerSpan * params.mSpanIndex;
+                top = getPaddingTop() + mCachedBorders[params.mSpanIndex];
                 bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
             }
             // We calculate everything with View's bounding box (which includes decor and margins)
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
index c02a0f6..3788ef6 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -127,6 +127,49 @@
         checkForMainThreadException();
     }
 
+    public void testCachedBorders() throws Throwable {
+        List<Config> testConfigurations = new ArrayList<Config>(mBaseVariations);
+        testConfigurations.addAll(cachedBordersTestConfigs());
+        for (Config config : testConfigurations) {
+            gridCachedBorderstTest(config);
+        }
+    }
+
+    private void gridCachedBorderstTest(Config config) throws Throwable {
+        RecyclerView recyclerView = setupBasic(config);
+        waitForFirstLayout(recyclerView);
+        final boolean vertical = config.mOrientation == GridLayoutManager.VERTICAL;
+        final int expectedSizeSum = vertical ? recyclerView.getWidth() : recyclerView.getHeight();
+        final int lastVisible = mGlm.findLastVisibleItemPosition();
+        for (int i = 0; i < lastVisible; i += config.mSpanCount) {
+            if ((i+1)*config.mSpanCount - 1 < lastVisible) {
+                int childrenSizeSum = 0;
+                for (int j = 0; j < config.mSpanCount; j++) {
+                    View child = recyclerView.getChildAt(i * config.mSpanCount + j);
+                    childrenSizeSum += vertical ? child.getWidth() : child.getHeight();
+                }
+                assertEquals(expectedSizeSum, childrenSizeSum);
+            }
+        }
+        removeRecyclerView();
+    }
+
+    private List<Config> cachedBordersTestConfigs() {
+        ArrayList<Config> configs = new ArrayList<Config>();
+        final int [] spanCounts = new int[]{88, 279, 741};
+        final int [] spanPerItem = new int[]{11, 9, 13};
+        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+            for (boolean reverseLayout : new boolean[]{false, true}) {
+                for (int i = 0 ; i < spanCounts.length; i++) {
+                    Config config = new Config(spanCounts[i], orientation, reverseLayout);
+                    config.mSpanPerItem = spanPerItem[i];
+                    configs.add(config);
+                }
+            }
+        }
+        return configs;
+    }
+
     public void testLayoutParams() throws Throwable {
         layoutParamsTest(GridLayoutManager.HORIZONTAL);
         removeRecyclerView();
@@ -630,6 +673,7 @@
         int mSpanCount;
         int mOrientation = GridLayoutManager.VERTICAL;
         int mItemCount = 1000;
+        int mSpanPerItem = 1;
         boolean mReverseLayout = false;
 
         Config(int spanCount, int itemCount) {
@@ -662,11 +706,17 @@
     class GridTestAdapter extends TestAdapter {
 
         Set<Integer> mFullSpanItems = new HashSet<Integer>();
+        int mSpanPerItem = 1;
 
         GridTestAdapter(int count) {
             super(count);
         }
 
+        GridTestAdapter(int count, int spanPerItem) {
+            super(count);
+            mSpanPerItem = spanPerItem;
+        }
+
         void setFullSpan(int... items) {
             for (int i : items) {
                 mFullSpanItems.add(i);
@@ -677,7 +727,7 @@
             glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                 @Override
                 public int getSpanSize(int position) {
-                    return mFullSpanItems.contains(position) ? glm.getSpanCount() : 1;
+                    return mFullSpanItems.contains(position) ? glm.getSpanCount() : mSpanPerItem;
                 }
             });
         }
diff --git a/v8/renderscript/Android.mk b/v8/renderscript/Android.mk
index 58f7219..a7755aa 100644
--- a/v8/renderscript/Android.mk
+++ b/v8/renderscript/Android.mk
@@ -24,7 +24,7 @@
 LOCAL_CFLAGS += -std=c++11
 
 LOCAL_MODULE := android-support-v8-renderscript
-LOCAL_SDK_VERSION := 19
+LOCAL_SDK_VERSION := 21
 LOCAL_SRC_FILES := $(call all-java-files-under, java/src)
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
index 5c20e5b..e97c330 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
@@ -72,7 +72,7 @@
     static Object lock = new Object();
 
     // Non-threadsafe functions.
-    native boolean nLoadSO(boolean useNative);
+    native boolean nLoadSO(boolean useNative, int deviceApi);
     native boolean nLoadIOSO();
     native long nDeviceCreate();
     native void nDeviceDestroy(long dev);
@@ -109,6 +109,13 @@
      * RenderScript layer or actually using the compatibility library.
      */
     static private boolean setupNative(int sdkVersion, Context ctx) {
+        // if targetSdkVersion is higher than the device api version, always use compat mode.
+        // Workaround for KK
+        if (android.os.Build.VERSION.SDK_INT < sdkVersion &&
+            android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
+            sNative = 0;
+        }
+
         if (sNative == -1) {
 
             // get the value of the debug.rs.forcecompat property
@@ -1184,7 +1191,7 @@
         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
             useIOlib = true;
         }
-        if (!rs.nLoadSO(useNative)) {
+        if (!rs.nLoadSO(useNative, android.os.Build.VERSION.SDK_INT)) {
             if (useNative) {
                 android.util.Log.v(LOG_TAG, "Unable to load libRS.so, falling back to compat mode");
                 useNative = false;
@@ -1195,7 +1202,7 @@
                 Log.e(LOG_TAG, "Error loading RS Compat library: " + e);
                 throw new RSRuntimeException("Error loading RS Compat library: " + e);
             }
-            if (!rs.nLoadSO(false)) {
+            if (!rs.nLoadSO(false, android.os.Build.VERSION.SDK_INT)) {
                 throw new RSRuntimeException("Error loading libRSSupport library");
             }
         }
diff --git a/v8/renderscript/jni/android_renderscript_RenderScript.cpp b/v8/renderscript/jni/android_renderscript_RenderScript.cpp
index 76f1876..38e5676 100644
--- a/v8/renderscript/jni/android_renderscript_RenderScript.cpp
+++ b/v8/renderscript/jni/android_renderscript_RenderScript.cpp
@@ -269,7 +269,7 @@
 // Incremental Support lib
 static dispatchTable dispatchTabInc;
 
-static jboolean nLoadSO(JNIEnv *_env, jobject _this, jboolean useNative) {
+static jboolean nLoadSO(JNIEnv *_env, jobject _this, jboolean useNative, jint deviceApi) {
     void* handle = NULL;
     if (useNative) {
         handle = dlopen("libRS.so", RTLD_LAZY | RTLD_LOCAL);
@@ -281,7 +281,7 @@
         return false;
     }
 
-    if (loadSymbols(handle, dispatchTab) == false) {
+    if (loadSymbols(handle, dispatchTab, deviceApi) == false) {
         LOG_API("%s init failed!", filename);
         return false;
     }
@@ -1601,7 +1601,7 @@
 static const char *classPathName = "android/support/v8/renderscript/RenderScript";
 
 static JNINativeMethod methods[] = {
-{"nLoadSO",                        "(Z)Z",                                    (bool*)nLoadSO },
+{"nLoadSO",                        "(ZI)Z",                                   (bool*)nLoadSO },
 {"nLoadIOSO",                      "()Z",                                     (bool*)nLoadIOSO },
 {"nDeviceCreate",                  "()J",                                     (void*)nDeviceCreate },
 {"nDeviceDestroy",                 "(J)V",                                    (void*)nDeviceDestroy },