Add MediaParceledListSlice as @SystemApi

Bug: 169399279
Test: make update-api -j; make -j;
Change-Id: I92ea0ade7093b86cc3a4253a20b129a0ec582ad9
diff --git a/apex/media/aidl/Android.bp b/apex/media/aidl/Android.bp
index 409a048..c2b00d5 100644
--- a/apex/media/aidl/Android.bp
+++ b/apex/media/aidl/Android.bp
@@ -15,21 +15,21 @@
 //
 
 filegroup {
-    name: "stable-mediasession2-aidl-srcs",
+    name: "stable-media-aidl-srcs",
     srcs: ["stable/**/*.aidl"],
     path: "stable",
 }
 
 filegroup {
-    name: "private-mediasession2-aidl-srcs",
+    name: "private-media-aidl-srcs",
     srcs: ["private/**/I*.aidl"],
     path: "private",
 }
 
 filegroup {
-    name: "mediasession2-aidl-srcs",
+    name: "media-aidl-srcs",
     srcs: [
-        ":private-mediasession2-aidl-srcs",
-        ":stable-mediasession2-aidl-srcs",
+        ":private-media-aidl-srcs",
+        ":stable-media-aidl-srcs",
     ],
 }
diff --git a/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl b/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl
new file mode 100644
index 0000000..92d673f
--- /dev/null
+++ b/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2020, 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 android.media;
+
+parcelable MediaParceledListSlice<T>;
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index ce4b030..813631e 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -54,9 +54,10 @@
 filegroup {
     name: "updatable-media-srcs",
     srcs: [
+        ":media-aidl-srcs",
+        ":mediaparceledlistslice-java-srcs",
         ":mediaparser-srcs",
         ":mediasession2-java-srcs",
-        ":mediasession2-aidl-srcs",
     ],
 }
 
@@ -77,6 +78,15 @@
 }
 
 filegroup {
+    name: "mediaparceledlistslice-java-srcs",
+    srcs: [
+        "java/android/media/MediaParceledListSlice.java",
+        "java/android/media/BaseMediaParceledListSlice.java",
+    ],
+    path: "java",
+}
+
+filegroup {
     name: "mediaparser-srcs",
     srcs: [
         "java/android/media/MediaParser.java"
diff --git a/apex/media/framework/api/module-lib-current.txt b/apex/media/framework/api/module-lib-current.txt
index d802177..d2826d0 100644
--- a/apex/media/framework/api/module-lib-current.txt
+++ b/apex/media/framework/api/module-lib-current.txt
@@ -1 +1,15 @@
 // Signature format: 2.0
+package android.media {
+
+  public final class MediaParceledListSlice<T extends android.os.Parcelable> implements android.os.Parcelable {
+    ctor public MediaParceledListSlice(@NonNull java.util.List<T>);
+    method public int describeContents();
+    method @NonNull public static <T extends android.os.Parcelable> android.media.MediaParceledListSlice<T> emptyList();
+    method public java.util.List<T> getList();
+    method public void setInlineCountLimit(int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.ClassLoaderCreator<android.media.MediaParceledListSlice> CREATOR;
+  }
+
+}
+
diff --git a/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java b/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java
new file mode 100644
index 0000000..fb66609
--- /dev/null
+++ b/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2020 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 android.media;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a copied version of BaseParceledListSlice in framework with hidden API usages
+ * removed.
+ *
+ * Transfer a large list of Parcelable objects across an IPC.  Splits into
+ * multiple transactions if needed.
+ *
+ * Caveat: for efficiency and security, all elements must be the same concrete type.
+ * In order to avoid writing the class name of each object, we must ensure that
+ * each object is the same type, or else unparceling then reparceling the data may yield
+ * a different result if the class name encoded in the Parcelable is a Base type.
+ * See b/17671747.
+ *
+ * @hide
+ */
+abstract class BaseMediaParceledListSlice<T> implements Parcelable {
+    private static String TAG = "BaseMediaParceledListSlice";
+    private static boolean DEBUG = false;
+
+    /*
+     * TODO get this number from somewhere else. For now set it to a quarter of
+     * the 1MB limit.
+     */
+    // private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
+    private static final int MAX_IPC_SIZE = 64 * 1024;
+
+    private final List<T> mList;
+
+    private int mInlineCountLimit = Integer.MAX_VALUE;
+
+    public BaseMediaParceledListSlice(List<T> list) {
+        mList = list;
+    }
+
+    @SuppressWarnings("unchecked")
+    BaseMediaParceledListSlice(Parcel p, ClassLoader loader) {
+        final int N = p.readInt();
+        mList = new ArrayList<T>(N);
+        if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
+        if (N <= 0) {
+            return;
+        }
+
+        Parcelable.Creator<?> creator = readParcelableCreator(p, loader);
+        Class<?> listElementClass = null;
+
+        int i = 0;
+        while (i < N) {
+            if (p.readInt() == 0) {
+                break;
+            }
+
+            final T parcelable = readCreator(creator, p, loader);
+            if (listElementClass == null) {
+                listElementClass = parcelable.getClass();
+            } else {
+                verifySameType(listElementClass, parcelable.getClass());
+            }
+
+            mList.add(parcelable);
+
+            if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
+            i++;
+        }
+        if (i >= N) {
+            return;
+        }
+        final IBinder retriever = p.readStrongBinder();
+        while (i < N) {
+            if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            data.writeInt(i);
+            try {
+                retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e);
+                return;
+            }
+            while (i < N && reply.readInt() != 0) {
+                final T parcelable = readCreator(creator, reply, loader);
+                verifySameType(listElementClass, parcelable.getClass());
+
+                mList.add(parcelable);
+
+                if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
+                i++;
+            }
+            reply.recycle();
+            data.recycle();
+        }
+    }
+
+    private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) {
+        if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
+            Parcelable.ClassLoaderCreator<?> classLoaderCreator =
+                    (Parcelable.ClassLoaderCreator<?>) creator;
+            return (T) classLoaderCreator.createFromParcel(p, loader);
+        }
+        return (T) creator.createFromParcel(p);
+    }
+
+    private static void verifySameType(final Class<?> expected, final Class<?> actual) {
+        if (!actual.equals(expected)) {
+            throw new IllegalArgumentException("Can't unparcel type "
+                    + (actual == null ? null : actual.getName()) + " in list of type "
+                    + (expected == null ? null : expected.getName()));
+        }
+    }
+
+    public List<T> getList() {
+        return mList;
+    }
+
+    /**
+     * Set a limit on the maximum number of entries in the array that will be included
+     * inline in the initial parcelling of this object.
+     */
+    public void setInlineCountLimit(int maxCount) {
+        mInlineCountLimit = maxCount;
+    }
+
+    /**
+     * Write this to another Parcel. Note that this discards the internal Parcel
+     * and should not be used anymore. This is so we can pass this to a Binder
+     * where we won't have a chance to call recycle on this.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        final int N = mList.size();
+        final int callFlags = flags;
+        dest.writeInt(N);
+        if (DEBUG) Log.d(TAG, "Writing " + N + " items");
+        if (N > 0) {
+            final Class<?> listElementClass = mList.get(0).getClass();
+            writeParcelableCreator(mList.get(0), dest);
+            int i = 0;
+            while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) {
+                dest.writeInt(1);
+
+                final T parcelable = mList.get(i);
+                verifySameType(listElementClass, parcelable.getClass());
+                writeElement(parcelable, dest, callFlags);
+
+                if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
+                i++;
+            }
+            if (i < N) {
+                dest.writeInt(0);
+                Binder retriever = new Binder() {
+                    @Override
+                    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                            throws RemoteException {
+                        if (code != FIRST_CALL_TRANSACTION) {
+                            return super.onTransact(code, data, reply, flags);
+                        }
+                        int i = data.readInt();
+                        if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
+                        while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
+                            reply.writeInt(1);
+
+                            final T parcelable = mList.get(i);
+                            verifySameType(listElementClass, parcelable.getClass());
+                            writeElement(parcelable, reply, callFlags);
+
+                            if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
+                            i++;
+                        }
+                        if (i < N) {
+                            if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
+                            reply.writeInt(0);
+                        }
+                        return true;
+                    }
+                };
+                if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
+                dest.writeStrongBinder(retriever);
+            }
+        }
+    }
+
+    abstract void writeElement(T parcelable, Parcel reply, int callFlags);
+
+    abstract void writeParcelableCreator(T parcelable, Parcel dest);
+
+    abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader);
+}
diff --git a/apex/media/framework/java/android/media/MediaParceledListSlice.java b/apex/media/framework/java/android/media/MediaParceledListSlice.java
new file mode 100644
index 0000000..e1223f6
--- /dev/null
+++ b/apex/media/framework/java/android/media/MediaParceledListSlice.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2020 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 android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This is a copied version of MediaParceledListSlice in framework with hidden API usages removed,
+ * and also with some lint error fixed.
+ *
+ * Transfer a large list of Parcelable objects across an IPC.  Splits into
+ * multiple transactions if needed.
+ *
+ * @see BaseMediaParceledListSlice
+ *
+ * TODO: Remove this from @SystemApi once all the MediaSession related classes are moved
+ *       to apex (or ParceledListSlice moved to apex). This class is temporaily added to system API
+ *       for moving classes step by step.
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class MediaParceledListSlice<T extends Parcelable>
+        extends BaseMediaParceledListSlice<T> {
+    public MediaParceledListSlice(@NonNull List<T> list) {
+        super(list);
+    }
+
+    private MediaParceledListSlice(Parcel in, ClassLoader loader) {
+        super(in, loader);
+    }
+
+    @NonNull
+    public static <T extends Parcelable> MediaParceledListSlice<T> emptyList() {
+        return new MediaParceledListSlice<T>(Collections.<T> emptyList());
+    }
+
+    @Override
+    public int describeContents() {
+        int contents = 0;
+        final List<T> list = getList();
+        for (int i=0; i<list.size(); i++) {
+            contents |= list.get(i).describeContents();
+        }
+        return contents;
+    }
+
+    @Override
+    void writeElement(T parcelable, Parcel dest, int callFlags) {
+        parcelable.writeToParcel(dest, callFlags);
+    }
+
+    @Override
+    void writeParcelableCreator(T parcelable, Parcel dest) {
+        dest.writeParcelableCreator((Parcelable) parcelable);
+    }
+
+    @Override
+    Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader) {
+        return from.readParcelableCreator(loader);
+    }
+
+    @NonNull
+    @SuppressWarnings("unchecked")
+    public static final Parcelable.ClassLoaderCreator<MediaParceledListSlice> CREATOR =
+            new Parcelable.ClassLoaderCreator<MediaParceledListSlice>() {
+        public MediaParceledListSlice createFromParcel(Parcel in) {
+            return new MediaParceledListSlice(in, null);
+        }
+
+        @Override
+        public MediaParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
+            return new MediaParceledListSlice(in, loader);
+        }
+
+        @Override
+        public MediaParceledListSlice[] newArray(int size) {
+            return new MediaParceledListSlice[size];
+        }
+    };
+}