Merge "Add range-based Parcel.compareData()" am: 98a4a1b8b9 am: e9eb2ecff1 am: dcfad1498f am: bddf602aca

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1854184

Change-Id: Ia79e802b1efb2068bc00cb7266dd5b6ff5433d92
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 1468d9f..61882ff 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -381,6 +381,8 @@
     private static native void nativeUnmarshall(
             long nativePtr, byte[] data, int offset, int length);
     private static native int nativeCompareData(long thisNativePtr, long otherNativePtr);
+    private static native boolean nativeCompareDataInRange(
+            long ptrA, int offsetA, long ptrB, int offsetB, int length);
     private static native void nativeAppendFrom(
             long thisNativePtr, long otherNativePtr, int offset, int length);
     @CriticalNative
@@ -678,11 +680,16 @@
     }
 
     /** @hide */
-    public final int compareData(Parcel other) {
+    public int compareData(Parcel other) {
         return nativeCompareData(mNativePtr, other.mNativePtr);
     }
 
     /** @hide */
+    public static boolean compareData(Parcel a, int offsetA, Parcel b, int offsetB, int length) {
+        return nativeCompareDataInRange(a.mNativePtr, offsetA, b.mNativePtr, offsetB, length);
+    }
+
+    /** @hide */
     public final void setClassCookie(Class clz, Object cookie) {
         if (mClassCookies == null) {
             mClassCookies = new ArrayMap<>();
@@ -3609,7 +3616,6 @@
         private final int mType;
         @Nullable private final ClassLoader mLoader;
         @Nullable private Object mObject;
-        @Nullable private volatile Parcel mValueParcel;
 
         /**
          * This goes from non-null to null once. Always check the nullability of this object before
@@ -3707,7 +3713,7 @@
                 return false;
             }
             // Finally we compare the payload.
-            return getValueParcel(source).compareData(value.getValueParcel(otherSource)) == 0;
+            return Parcel.compareData(source, mPosition, otherSource, value.mPosition, mLength);
         }
 
         @Override
@@ -3715,17 +3721,6 @@
             // Accessing mSource first to provide memory barrier for mObject
             return Objects.hash(mSource == null, mObject, mLoader, mType, mLength);
         }
-
-        /** This extracts the parcel section responsible for the object and returns it. */
-        private Parcel getValueParcel(Parcel source) {
-            Parcel parcel = mValueParcel;
-            if (parcel == null) {
-                parcel = Parcel.obtain();
-                parcel.appendFrom(source, mPosition, mLength);
-                mValueParcel = parcel;
-            }
-            return parcel;
-        }
     }
 
     /**
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index be9aaaf..0d530f6 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -604,6 +604,25 @@
     return thisParcel->compareData(*otherParcel);
 }
 
+static jboolean android_os_Parcel_compareDataInRange(JNIEnv* env, jclass clazz, jlong thisNativePtr,
+                                                     jint thisOffset, jlong otherNativePtr,
+                                                     jint otherOffset, jint length) {
+    Parcel* thisParcel = reinterpret_cast<Parcel*>(thisNativePtr);
+    LOG_ALWAYS_FATAL_IF(thisParcel == nullptr, "Should not be null");
+
+    Parcel* otherParcel = reinterpret_cast<Parcel*>(otherNativePtr);
+    LOG_ALWAYS_FATAL_IF(otherParcel == nullptr, "Should not be null");
+
+    int result;
+    status_t err =
+            thisParcel->compareDataInRange(thisOffset, *otherParcel, otherOffset, length, &result);
+    if (err != NO_ERROR) {
+        signalExceptionForError(env, clazz, err);
+        return JNI_FALSE;
+    }
+    return (result == 0) ? JNI_TRUE : JNI_FALSE;
+}
+
 static void android_os_Parcel_appendFrom(JNIEnv* env, jclass clazz, jlong thisNativePtr,
                                           jlong otherNativePtr, jint offset, jint length)
 {
@@ -841,6 +860,7 @@
     {"nativeMarshall",            "(J)[B", (void*)android_os_Parcel_marshall},
     {"nativeUnmarshall",          "(J[BII)V", (void*)android_os_Parcel_unmarshall},
     {"nativeCompareData",         "(JJ)I", (void*)android_os_Parcel_compareData},
+    {"nativeCompareDataInRange",  "(JIJII)Z", (void*)android_os_Parcel_compareDataInRange},
     {"nativeAppendFrom",          "(JJII)V", (void*)android_os_Parcel_appendFrom},
     // @CriticalNative
     {"nativeHasFileDescriptors",  "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index ddd0070..09f4840 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -273,16 +273,21 @@
         Parcelable p1 = new CustomParcelable(13, "Tiramisu");
         Parcelable p2 = new CustomParcelable(13, "Tiramisu");
         Bundle a = new Bundle();
-        a.putParcelable("key", p1);
+        a.putParcelable("key1", p1);
         a.readFromParcel(getParcelledBundle(a));
         a.setClassLoader(getClass().getClassLoader());
         Bundle b = new Bundle();
-        b.putParcelable("key", p2);
+        // Adding extra element so that the position of the elements of interest in their respective
+        // source parcels are different so we can cover that case of Parcel.compareData(). We'll
+        // remove the element later so the map is equal.
+        b.putString("key0", "string");
+        b.putParcelable("key1", p2);
         b.readFromParcel(getParcelledBundle(b));
         b.setClassLoader(getClass().getClassLoader());
-        // 2 lazy values with identical parcels inside
         a.isEmpty();
         b.isEmpty();
+        b.remove("key0");
+        // 2 lazy values with identical parcels inside
 
         assertTrue(Bundle.kindofEquals(a, b));
     }
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index dcb3e2f..fdd278b 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -17,6 +17,9 @@
 package android.os;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.Presubmit;
 
@@ -110,4 +113,130 @@
             assertEquals(string, p.readString16());
         }
     }
+
+    @Test
+    public void testCompareDataInRange_whenSameData() {
+        Parcel pA = Parcel.obtain();
+        int iA = pA.dataPosition();
+        pA.writeInt(13);
+        pA.writeString("Tiramisu");
+        int length = pA.dataPosition() - iA;
+        Parcel pB = Parcel.obtain();
+        pB.writeString("Prefix");
+        int iB = pB.dataPosition();
+        pB.writeInt(13);
+        pB.writeString("Tiramisu");
+
+        assertTrue(Parcel.compareData(pA, iA, pB, iB, length));
+    }
+
+    @Test
+    public void testCompareDataInRange_whenSameDataWithBinder() {
+        Binder binder = new Binder();
+        Parcel pA = Parcel.obtain();
+        int iA = pA.dataPosition();
+        pA.writeInt(13);
+        pA.writeStrongBinder(binder);
+        pA.writeString("Tiramisu");
+        int length = pA.dataPosition() - iA;
+        Parcel pB = Parcel.obtain();
+        pB.writeString("Prefix");
+        int iB = pB.dataPosition();
+        pB.writeInt(13);
+        pB.writeStrongBinder(binder);
+        pB.writeString("Tiramisu");
+
+        assertTrue(Parcel.compareData(pA, iA, pB, iB, length));
+    }
+
+    @Test
+    public void testCompareDataInRange_whenDifferentData() {
+        Parcel pA = Parcel.obtain();
+        int iA = pA.dataPosition();
+        pA.writeInt(13);
+        pA.writeString("Tiramisu");
+        int length = pA.dataPosition() - iA;
+        Parcel pB = Parcel.obtain();
+        int iB = pB.dataPosition();
+        pB.writeString("Prefix");
+        pB.writeInt(13);
+        pB.writeString("Tiramisu");
+
+        assertFalse(Parcel.compareData(pA, iA, pB, iB, length));
+    }
+
+    @Test
+    public void testCompareDataInRange_whenLimitOutOfBounds_throws() {
+        Parcel pA = Parcel.obtain();
+        int iA = pA.dataPosition();
+        pA.writeInt(12);
+        pA.writeString("Tiramisu");
+        int length = pA.dataPosition() - iA;
+        Parcel pB = Parcel.obtain();
+        pB.writeString("Prefix");
+        int iB = pB.dataPosition();
+        pB.writeInt(13);
+        pB.writeString("Tiramisu");
+        pB.writeInt(-1);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> Parcel.compareData(pA, iA + length, pB, iB, 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> Parcel.compareData(pA, iA, pB, pB.dataSize(), 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> Parcel.compareData(pA, iA, pB, iB, length + 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> Parcel.compareData(pA, iA + length + 1, pB, iB, 0));
+        assertThrows(IllegalArgumentException.class,
+                () -> Parcel.compareData(pA, iA, pB, iB + pB.dataSize() + 1, 0));
+    }
+
+    @Test
+    public void testCompareDataInRange_whenLengthZero() {
+        Parcel pA = Parcel.obtain();
+        int iA = pA.dataPosition();
+        pA.writeInt(12);
+        pA.writeString("Tiramisu");
+        int length = pA.dataPosition() - iA;
+        Parcel pB = Parcel.obtain();
+        pB.writeString("Prefix");
+        int iB = pB.dataPosition();
+        pB.writeInt(13);
+        pB.writeString("Tiramisu");
+
+        assertTrue(Parcel.compareData(pA, 0, pB, iB, 0));
+        assertTrue(Parcel.compareData(pA, iA + length, pB, iB, 0));
+        assertTrue(Parcel.compareData(pA, iA, pB, pB.dataSize(), 0));
+    }
+
+    @Test
+    public void testCompareDataInRange_whenNegativeLength_throws() {
+        Parcel pA = Parcel.obtain();
+        int iA = pA.dataPosition();
+        pA.writeInt(12);
+        pA.writeString("Tiramisu");
+        Parcel pB = Parcel.obtain();
+        pB.writeString("Prefix");
+        int iB = pB.dataPosition();
+        pB.writeInt(13);
+        pB.writeString("Tiramisu");
+
+        assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, iA, pB, iB, -1));
+    }
+
+    @Test
+    public void testCompareDataInRange_whenNegativeOffset_throws() {
+        Parcel pA = Parcel.obtain();
+        int iA = pA.dataPosition();
+        pA.writeInt(12);
+        pA.writeString("Tiramisu");
+        Parcel pB = Parcel.obtain();
+        pB.writeString("Prefix");
+        int iB = pB.dataPosition();
+        pB.writeInt(13);
+        pB.writeString("Tiramisu");
+
+        assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, -1, pB, iB, 0));
+        assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, 0, pB, -1, 0));
+    }
 }