AML: Replace ParceledListSlice

This CL creates a new class named MediaParceledListSlice that
replaces ParceledListSlice. MediaParceledListSlice is mostly copied
from ParcelImplListSlice.java with a few adjustments:
  1) not specifying type of parcelable (to work for both
     MediaSession.QueueItem & MediaBrowser.Mediaitem)
  2) Setting parameter to null for calling Parcel#readParcelable.
     Parcel retrieves the class loader inside readParcelableCreator.

Bug: 119750807
Test: mmm . (under frameworks/av/packages/MediaComponents)
Change-Id: If234308724ba132140089835e66b3948aacc4e57
diff --git a/packages/MediaComponents/apex/Android.bp b/packages/MediaComponents/apex/Android.bp
index e797e14..d89eb77 100644
--- a/packages/MediaComponents/apex/Android.bp
+++ b/packages/MediaComponents/apex/Android.bp
@@ -9,6 +9,8 @@
         // "Refusing to generate code with unstructured parcelables."
         "java/android/media/MediaDescription.aidl",
         "java/android/media/MediaMetadata.aidl",
+        // TODO(insun): check why MediaParceledListSlice.aidl should be added here
+        "java/android/media/MediaParceledListSlice.aidl",
         "java/android/media/Rating.aidl",
         "java/android/media/browse/MediaBrowser.aidl",
         "java/android/media/session/MediaSession.aidl",
diff --git a/packages/MediaComponents/apex/java/android/media/MediaParceledListSlice.aidl b/packages/MediaComponents/apex/java/android/media/MediaParceledListSlice.aidl
new file mode 100644
index 0000000..228ea9c
--- /dev/null
+++ b/packages/MediaComponents/apex/java/android/media/MediaParceledListSlice.aidl
@@ -0,0 +1,19 @@
+/* Copyright (C) 2018, 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;
+
+/** @hide */
+parcelable MediaParceledListSlice;
\ No newline at end of file
diff --git a/packages/MediaComponents/apex/java/android/media/MediaParceledListSlice.java b/packages/MediaComponents/apex/java/android/media/MediaParceledListSlice.java
new file mode 100644
index 0000000..ec3fdb7
--- /dev/null
+++ b/packages/MediaComponents/apex/java/android/media/MediaParceledListSlice.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2018 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.UnsupportedAppUsage;
+import android.os.Binder;
+import android.os.Build;
+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.Collections;
+import java.util.List;
+
+/**
+ * Transfer a large list of objects across an IPC. Splits into multiple transactions if needed.
+ * Note: Only use classes declared final in order to avoid subclasses overriding reading/writing
+ * parcel logic.
+ *
+ * TODO: Add test for sending large data
+ * @hide
+ */
+public class MediaParceledListSlice<T extends Parcelable> implements Parcelable {
+    private static final String TAG = "MediaParceledListSlice";
+    private static final boolean DEBUG = false;
+
+    private static final int MAX_IPC_SIZE = 64 * 1024; // IBinder.MAX_IPC_SIZE
+
+    final List<T> mList;
+
+    public MediaParceledListSlice(List<T> list) {
+        if (list == null) {
+            throw new IllegalArgumentException("list shouldn't be null");
+        }
+        mList = list;
+    }
+
+    MediaParceledListSlice(Parcel p) {
+        final int itemCount = p.readInt();
+        mList = new ArrayList<>(itemCount);
+        if (DEBUG) {
+            Log.d(TAG, "Retrieving " + itemCount + " items");
+        }
+        if (itemCount <= 0) {
+            return;
+        }
+
+        int i = 0;
+        while (i < itemCount) {
+            if (p.readInt() == 0) {
+                break;
+            }
+
+            final T parcelable = p.readParcelable(null);
+            mList.add(parcelable);
+
+            if (DEBUG) {
+                Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size() - 1));
+            }
+            i++;
+        }
+        if (i >= itemCount) {
+            return;
+        }
+        final IBinder retriever = p.readStrongBinder();
+        while (i < itemCount) {
+            if (DEBUG) {
+                Log.d(TAG, "Reading more @" + i + " of " + itemCount + ": 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 " + itemCount, e);
+                return;
+            }
+            while (i < itemCount && reply.readInt() != 0) {
+                final T parcelable = reply.readParcelable(null);
+                mList.add(parcelable);
+
+                if (DEBUG) {
+                    Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size() - 1));
+                }
+                i++;
+            }
+            reply.recycle();
+            data.recycle();
+        }
+    }
+
+    public List<T> getList() {
+        return mList;
+    }
+
+    /**
+     * 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 itemCount = mList.size();
+        dest.writeInt(itemCount);
+        if (DEBUG) {
+            Log.d(TAG, "Writing " + itemCount + " items");
+        }
+        if (itemCount > 0) {
+            int i = 0;
+            while (i < itemCount && dest.dataSize() < MAX_IPC_SIZE) {
+                dest.writeInt(1);
+
+                final T parcelable = mList.get(i);
+                dest.writeParcelable(parcelable, flags);
+
+                if (DEBUG) {
+                    Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
+                }
+                i++;
+            }
+            if (i < itemCount) {
+                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 " + itemCount);
+                        }
+                        while (i < itemCount && reply.dataSize() < MAX_IPC_SIZE) {
+                            reply.writeInt(1);
+
+                            final T parcelable = mList.get(i);
+                            reply.writeParcelable(parcelable, flags);
+
+                            if (DEBUG) {
+                                Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
+                            }
+                            i++;
+                        }
+                        if (i < itemCount) {
+                            if (DEBUG) {
+                                Log.d(TAG, "Breaking @" + i + " of " + itemCount);
+                            }
+                            reply.writeInt(0);
+                        }
+                        return true;
+                    }
+                };
+                if (DEBUG) {
+                    Log.d(TAG, "Breaking @" + i + " of " + itemCount + ": retriever=" + retriever);
+                }
+                dest.writeStrongBinder(retriever);
+            }
+        }
+    }
+
+    @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;
+    }
+
+    public static final Parcelable.Creator<MediaParceledListSlice> CREATOR =
+            new Parcelable.Creator<MediaParceledListSlice>() {
+        @Override
+        public MediaParceledListSlice createFromParcel(Parcel in) {
+            return new MediaParceledListSlice(in);
+        }
+
+        @Override
+        public MediaParceledListSlice[] newArray(int size) {
+            return new MediaParceledListSlice[size];
+        }
+    };
+}
diff --git a/packages/MediaComponents/apex/java/android/media/browse/MediaBrowser.java b/packages/MediaComponents/apex/java/android/media/browse/MediaBrowser.java
index 4e091ad..b1b14c6 100644
--- a/packages/MediaComponents/apex/java/android/media/browse/MediaBrowser.java
+++ b/packages/MediaComponents/apex/java/android/media/browse/MediaBrowser.java
@@ -23,8 +23,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-//import android.content.pm.ParceledListSlice;
 import android.media.MediaDescription;
+import android.media.MediaParceledListSlice;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.os.Binder;
@@ -652,10 +652,8 @@
         });
     }
 
-    //TODO:(b/119750807) Resolve hidden API usage ParceledListSlice.
-    /*
     private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
-            final String parentId, final ParceledListSlice list, final Bundle options) {
+            final String parentId, final MediaParceledListSlice list, final Bundle options) {
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -699,7 +697,6 @@
             }
         });
     }
-    */
 
     /**
      * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
@@ -1109,22 +1106,19 @@
             }
         }
 
-        //TODO:(b/119750807) Resolve hidden API usage ParceledListSlice.
-        /*
         @Override
-        public void onLoadChildren(String parentId, ParceledListSlice list) {
+        public void onLoadChildren(String parentId, MediaParceledListSlice list) {
             onLoadChildrenWithOptions(parentId, list, null);
         }
 
         @Override
-        public void onLoadChildrenWithOptions(String parentId, ParceledListSlice list,
+        public void onLoadChildrenWithOptions(String parentId, MediaParceledListSlice list,
                 final Bundle options) {
             MediaBrowser mediaBrowser = mMediaBrowser.get();
             if (mediaBrowser != null) {
                 mediaBrowser.onLoadChildren(this, parentId, list, options);
             }
         }
-        */
     }
 
     private static class Subscription {
diff --git a/packages/MediaComponents/apex/java/android/media/session/ISession.aidl b/packages/MediaComponents/apex/java/android/media/session/ISession.aidl
index cbd93cb..c5b376c 100644
--- a/packages/MediaComponents/apex/java/android/media/session/ISession.aidl
+++ b/packages/MediaComponents/apex/java/android/media/session/ISession.aidl
@@ -16,9 +16,9 @@
 package android.media.session;
 
 import android.app.PendingIntent;
-import android.content.pm.ParceledListSlice;
 //import android.media.AudioAttributes;
 import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
 import android.media.session.ISessionController;
 import android.media.session.PlaybackState;
 import android.media.session.MediaSession;
@@ -41,8 +41,7 @@
     // These commands are for the TransportPerformer
     void setMetadata(in MediaMetadata metadata);
     void setPlaybackState(in PlaybackState state);
-    //TODO(b/119750807): Resolve hidden API usage ParceledListSlice.
-    //void setQueue(in ParceledListSlice queue);
+    void setQueue(in MediaParceledListSlice queue);
     void setQueueTitle(CharSequence title);
     void setExtras(in Bundle extras);
     void setRatingType(int type);
diff --git a/packages/MediaComponents/apex/java/android/media/session/ISessionController.aidl b/packages/MediaComponents/apex/java/android/media/session/ISessionController.aidl
index 031a388..74897f7 100644
--- a/packages/MediaComponents/apex/java/android/media/session/ISessionController.aidl
+++ b/packages/MediaComponents/apex/java/android/media/session/ISessionController.aidl
@@ -17,8 +17,8 @@
 
 import android.app.PendingIntent;
 import android.content.Intent;
-//import android.content.pm.ParceledListSlice;
 import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
 import android.media.Rating;
 import android.media.session.ISessionControllerCallback;
 import android.media.session.MediaSession;
@@ -81,8 +81,7 @@
             String action, in Bundle args);
     MediaMetadata getMetadata();
     PlaybackState getPlaybackState();
-    //TODO:(b/119750807) Resolve hidden API usage ParceledListSlice.
-    //ParceledListSlice getQueue();
+    MediaParceledListSlice getQueue();
     CharSequence getQueueTitle();
     Bundle getExtras();
     int getRatingType();
diff --git a/packages/MediaComponents/apex/java/android/media/session/ISessionControllerCallback.aidl b/packages/MediaComponents/apex/java/android/media/session/ISessionControllerCallback.aidl
index 173504b..f5cc4f6 100644
--- a/packages/MediaComponents/apex/java/android/media/session/ISessionControllerCallback.aidl
+++ b/packages/MediaComponents/apex/java/android/media/session/ISessionControllerCallback.aidl
@@ -15,8 +15,8 @@
 
 package android.media.session;
 
-//import android.content.pm.ParceledListSlice;
 import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
 import android.media.session.ParcelableVolumeInfo;
 import android.media.session.PlaybackState;
 import android.media.session.MediaSession;
@@ -32,8 +32,7 @@
     // These callbacks are for the TransportController
     void onPlaybackStateChanged(in PlaybackState state);
     void onMetadataChanged(in MediaMetadata metadata);
-    //TODO:(b/119750807) Resolve hidden API usage ParceledListSlice.
-    //void onQueueChanged(in ParceledListSlice queue);
+    void onQueueChanged(in MediaParceledListSlice queue);
     void onQueueTitleChanged(CharSequence title);
     void onExtrasChanged(in Bundle extras);
     void onVolumeInfoChanged(in ParcelableVolumeInfo info);
diff --git a/packages/MediaComponents/apex/java/android/media/session/MediaController.java b/packages/MediaComponents/apex/java/android/media/session/MediaController.java
index 60f74ab..8c3a013 100644
--- a/packages/MediaComponents/apex/java/android/media/session/MediaController.java
+++ b/packages/MediaComponents/apex/java/android/media/session/MediaController.java
@@ -21,10 +21,10 @@
 import android.annotation.UnsupportedAppUsage;
 import android.app.PendingIntent;
 import android.content.Context;
-//import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
 import android.media.Rating;
 import android.media.VolumeProvider;
 import android.net.Uri;
@@ -243,17 +243,14 @@
      * @return The current play queue or null.
      */
     public @Nullable List<MediaSession.QueueItem> getQueue() {
-        //TODO:(b/119750807) Resolve hidden API usage ParceledListSlice.
-        /*
         try {
-            ParceledListSlice queue = mSessionBinder.getQueue();
+            MediaParceledListSlice queue = mSessionBinder.getQueue();
             if (queue != null) {
                 return queue.getList();
             }
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error calling getQueue.", e);
         }
-        */
         return null;
     }
 
@@ -1102,10 +1099,8 @@
             }
         }
 
-        //TODO:(b/119750807) Resolve hidden API usage ParceledListSlice.
-        /*
         @Override
-        public void onQueueChanged(ParceledListSlice parceledQueue) {
+        public void onQueueChanged(MediaParceledListSlice parceledQueue) {
             List<MediaSession.QueueItem> queue = parceledQueue == null ? null : parceledQueue
                     .getList();
             MediaController controller = mController.get();
@@ -1113,7 +1108,6 @@
                 controller.postMessage(MSG_UPDATE_QUEUE, queue, null);
             }
         }
-        */
 
         @Override
         public void onQueueTitleChanged(CharSequence title) {
diff --git a/packages/MediaComponents/apex/java/android/media/session/MediaSession.java b/packages/MediaComponents/apex/java/android/media/session/MediaSession.java
index 1ae1d2c..e5eff95 100644
--- a/packages/MediaComponents/apex/java/android/media/session/MediaSession.java
+++ b/packages/MediaComponents/apex/java/android/media/session/MediaSession.java
@@ -24,10 +24,10 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-//import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
 import android.media.Rating;
 import android.media.VolumeProvider;
 import android.net.Uri;
@@ -461,14 +461,11 @@
      * @param queue A list of items in the play queue.
      */
     public void setQueue(@Nullable List<QueueItem> queue) {
-        //TODO:(b/119750807) Resolve hidden API usage ParceledListSlice.
-        /*
         try {
-            mBinder.setQueue(queue == null ? null : new ParceledListSlice<QueueItem>(queue));
+            mBinder.setQueue(queue == null ? null : new MediaParceledListSlice<QueueItem>(queue));
         } catch (RemoteException e) {
             Log.wtf("Dead object in setQueue.", e);
         }
-        */
     }
 
     /**
diff --git a/packages/MediaComponents/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/packages/MediaComponents/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
index bcc2826..8dc480d 100644
--- a/packages/MediaComponents/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
+++ b/packages/MediaComponents/apex/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
@@ -2,8 +2,8 @@
 
 package android.service.media;
 
-//import android.content.pm.ParceledListSlice;
 import android.graphics.Bitmap;
+import android.media.MediaParceledListSlice;
 import android.media.session.MediaSession;
 import android.os.Bundle;
 
@@ -22,7 +22,7 @@
      */
     void onConnect(String root, in MediaSession.Token session, in Bundle extras);
     void onConnectFailed();
-    //TODO:(b/119750807) Resolve hidden API usage ParceledListSlice.
-    //void onLoadChildren(String mediaId, in ParceledListSlice list);
-    //void onLoadChildrenWithOptions(String mediaId, in ParceledListSlice list, in Bundle options);
+    void onLoadChildren(String mediaId, in MediaParceledListSlice list);
+    void onLoadChildrenWithOptions(String mediaId, in MediaParceledListSlice list,
+            in Bundle options);
 }
diff --git a/packages/MediaComponents/apex/java/android/service/media/MediaBrowserService.java b/packages/MediaComponents/apex/java/android/service/media/MediaBrowserService.java
index fa7696e..a66ec35 100644
--- a/packages/MediaComponents/apex/java/android/service/media/MediaBrowserService.java
+++ b/packages/MediaComponents/apex/java/android/service/media/MediaBrowserService.java
@@ -25,7 +25,7 @@
 import android.app.Service;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-//import android.content.pm.ParceledListSlice;
+import android.media.MediaParceledListSlice;
 import android.media.browse.MediaBrowser;
 import android.media.browse.MediaBrowserUtils;
 import android.media.session.MediaSession;
@@ -687,10 +687,8 @@
                 List<MediaBrowser.MediaItem> filteredList =
                         (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0
                         ? applyOptions(list, options) : list;
-                //TODO:(b/119750807) Resolve hidden API usage ParceledListSlice.
-                /*
-                final ParceledListSlice<MediaBrowser.MediaItem> pls =
-                        filteredList == null ? null : new ParceledListSlice<>(filteredList);
+                final MediaParceledListSlice<MediaBrowser.MediaItem> pls =
+                        filteredList == null ? null : new MediaParceledListSlice<>(filteredList);
                 try {
                     connection.callbacks.onLoadChildrenWithOptions(parentId, pls, options);
                 } catch (RemoteException ex) {
@@ -698,7 +696,6 @@
                     Log.w(TAG, "Calling onLoadChildren() failed for id=" + parentId
                             + " package=" + connection.pkg);
                 }
-                */
             }
         };