Deliver start service args with ParcelledListSlice.

We have seen issues where we fail restarting a process
because there are so many services with so many pending
start arguments that we hit IPC limits.  So instead of
doing an IPC for each service start, collect them together
in a list that is sent once, and send it inside of a
ParcelledListSlice to control how much data is written
inline in the IPC.

Test: boot and ran
Change-Id: Ifed26ccdf535871e577fc02c7ef1d09060ab06ca
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e49aad2..d8e5742 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -38,6 +38,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.AssetManager;
@@ -868,16 +869,20 @@
             sendMessage(H.UNBIND_SERVICE, s);
         }
 
-        public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
-            int flags ,Intent args) {
-            ServiceArgsData s = new ServiceArgsData();
-            s.token = token;
-            s.taskRemoved = taskRemoved;
-            s.startId = startId;
-            s.flags = flags;
-            s.args = args;
+        public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) {
+            List<ServiceStartArgs> list = args.getList();
 
-            sendMessage(H.SERVICE_ARGS, s);
+            for (int i = 0; i < list.size(); i++) {
+                ServiceStartArgs ssa = list.get(i);
+                ServiceArgsData s = new ServiceArgsData();
+                s.token = token;
+                s.taskRemoved = ssa.taskRemoved;
+                s.startId = ssa.startId;
+                s.flags = ssa.flags;
+                s.args = ssa.args;
+
+                sendMessage(H.SERVICE_ARGS, s);
+            }
         }
 
         public final void scheduleStopService(IBinder token) {
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 6c43fe3..1b3c00b 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.CompatibilityInfo;
@@ -86,8 +87,7 @@
             in Bundle coreSettings, in String buildSerial);
     void scheduleExit();
     void scheduleConfigurationChanged(in Configuration config);
-    void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
-            int flags, in Intent args);
+    void scheduleServiceArgs(IBinder token, in ParceledListSlice args);
     void updateTimeZone();
     void processInBackground();
     void scheduleBindService(IBinder token,
diff --git a/core/java/android/app/ServiceStartArgs.aidl b/core/java/android/app/ServiceStartArgs.aidl
new file mode 100644
index 0000000..fd2cf0f
--- /dev/null
+++ b/core/java/android/app/ServiceStartArgs.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 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.app;
+
+/** @hide */
+parcelable ServiceStartArgs;
diff --git a/core/java/android/app/ServiceStartArgs.java b/core/java/android/app/ServiceStartArgs.java
new file mode 100644
index 0000000..f030cba
--- /dev/null
+++ b/core/java/android/app/ServiceStartArgs.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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.app;
+
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Describes a Service.onStartCommand() request from the system.
+ * @hide
+ */
+public class ServiceStartArgs implements Parcelable {
+    final public boolean taskRemoved;
+    final public int startId;
+    final public int flags;
+    final public Intent args;
+
+    public ServiceStartArgs(boolean _taskRemoved, int _startId, int _flags, Intent _args) {
+        taskRemoved = _taskRemoved;
+        startId = _startId;
+        flags = _flags;
+        args = _args;
+    }
+
+    public String toString() {
+        return "ServiceStartArgs{taskRemoved=" + taskRemoved + ", startId=" + startId
+                + ", flags=0x" + Integer.toHexString(flags) + ", args=" + args + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(taskRemoved ? 1 : 0);
+        out.writeInt(startId);
+        out.writeInt(flags);
+        if (args != null) {
+            out.writeInt(1);
+            args.writeToParcel(out, 0);
+        } else {
+            out.writeInt(0);
+        }
+    }
+
+    public static final Parcelable.Creator<ServiceStartArgs> CREATOR
+            = new Parcelable.Creator<ServiceStartArgs>() {
+        public ServiceStartArgs createFromParcel(Parcel in) {
+            return new ServiceStartArgs(in);
+        }
+
+        public ServiceStartArgs[] newArray(int size) {
+            return new ServiceStartArgs[size];
+        }
+    };
+
+    public ServiceStartArgs(Parcel in) {
+        taskRemoved = in.readInt() != 0;
+        startId = in.readInt();
+        flags = in.readInt();
+        if (in.readInt() != 0) {
+            args = Intent.CREATOR.createFromParcel(in);
+        } else {
+            args = null;
+        }
+    }
+}
diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java
index c4e4e06..aaa5f19 100644
--- a/core/java/android/content/pm/BaseParceledListSlice.java
+++ b/core/java/android/content/pm/BaseParceledListSlice.java
@@ -50,6 +50,8 @@
 
     private final List<T> mList;
 
+    private int mInlineCountLimit = Integer.MAX_VALUE;
+
     public BaseParceledListSlice(List<T> list) {
         mList = list;
     }
@@ -135,6 +137,14 @@
     }
 
     /**
+     * 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.
@@ -149,7 +159,7 @@
             final Class<?> listElementClass = mList.get(0).getClass();
             writeParcelableCreator(mList.get(0), dest);
             int i = 0;
-            while (i < N && dest.dataSize() < MAX_IPC_SIZE) {
+            while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) {
                 dest.writeInt(1);
 
                 final T parcelable = mList.get(i);