Provide remote stack trace information

The stack is truncated up to 5 lines at parcel time. When unparceling,
a separate RemoteException will be created and set as a cause of the
exception being thrown.

Performance results(in nanoseconds):
timeWriteExceptionWithStackTraceParceling  4168
timeWriteException                         2201
timeReadException                         15878
timeReadExceptionWithStackTraceParceling  23805

Test: manual + ParcelPerfTest
Bug: 36561158
Change-Id: I18b64a6c39c24ab067115874ddb5bd71f556a601
diff --git a/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
index a92597f..6e4c9c5 100644
--- a/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
@@ -27,6 +27,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
 @RunWith(AndroidJUnit4.class)
 @LargeTest
 public class ParcelPerfTest {
@@ -167,4 +171,80 @@
             Parcel.obtain().recycle();
         }
     }
+
+    @Test
+    public void timeWriteException() {
+        timeWriteException(false);
+    }
+
+    @Test
+    public void timeWriteExceptionWithStackTraceParceling() {
+        timeWriteException(true);
+    }
+
+    @Test
+    public void timeReadException() {
+        timeReadException(false);
+    }
+
+    @Test
+    public void timeReadExceptionWithStackTraceParceling() {
+        timeReadException(true);
+    }
+
+    private void timeWriteException(boolean enableParceling) {
+        if (enableParceling) {
+            Parcel.setStackTraceParceling(true);
+        }
+        try {
+            final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+            Parcel p = Parcel.obtain();
+            SecurityException e = new SecurityException("TestMessage");
+            while (state.keepRunning()) {
+                p.setDataPosition(0);
+                p.writeException(e);
+            }
+        } finally {
+            if (enableParceling) {
+                Parcel.setStackTraceParceling(false);
+            }
+        }
+    }
+
+    private void timeReadException(boolean enableParceling) {
+        if (enableParceling) {
+            Parcel.setStackTraceParceling(true);
+        }
+        try {
+            final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+            Parcel p = Parcel.obtain();
+            String msg = "TestMessage";
+            p.writeException(new SecurityException(msg));
+            p.setDataPosition(0);
+            // First verify that remote cause is set (if parceling is enabled)
+            try {
+                p.readException();
+            } catch (SecurityException e) {
+                assertEquals(e.getMessage(), msg);
+                if (enableParceling) {
+                    assertTrue(e.getCause() instanceof RemoteException);
+                } else {
+                    assertNull(e.getCause());
+                }
+            }
+
+            while (state.keepRunning()) {
+                p.setDataPosition(0);
+                try {
+                    p.readException();
+                } catch (SecurityException expected) {
+                }
+            }
+        } finally {
+            if (enableParceling) {
+                Parcel.setStackTraceParceling(false);
+            }
+        }
+    }
+
 }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 10adb5a..d092018 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -191,6 +191,7 @@
  * {@link #readSparseArray(ClassLoader)}.
  */
 public final class Parcel {
+
     private static final boolean DEBUG_RECYCLE = false;
     private static final boolean DEBUG_ARRAY_MAP = false;
     private static final String TAG = "Parcel";
@@ -209,6 +210,12 @@
 
     private RuntimeException mStack;
 
+    /**
+     * Whether or not to parcel the stack trace of an exception. This has a performance
+     * impact, so should only be included in specific processes and only on debug builds.
+     */
+    private static boolean sParcelExceptionStackTrace;
+
     private static final int POOL_SIZE = 6;
     private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];
     private static final Parcel[] sHolderPool = new Parcel[POOL_SIZE];
@@ -325,6 +332,11 @@
     private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
     private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
 
+    /** Last time exception with a stack trace was written */
+    private static volatile long sLastWriteExceptionStackTrace;
+    /** Used for throttling of writing stack trace, which is costly */
+    private static final int WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS = 1000;
+
     @CriticalNative
     private static native long nativeGetBlobAshmemSize(long nativePtr);
 
@@ -1696,6 +1708,11 @@
         }
     }
 
+    /** @hide For debugging purposes */
+    public static void setStackTraceParceling(boolean enabled) {
+        sParcelExceptionStackTrace = enabled;
+    }
+
     /**
      * Special function for writing an exception result at the header of
      * a parcel, to be used when returning an exception from a transaction.
@@ -1753,6 +1770,27 @@
             throw new RuntimeException(e);
         }
         writeString(e.getMessage());
+        final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
+        if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
+                > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
+            sLastWriteExceptionStackTrace = timeNow;
+            final int sizePosition = dataPosition();
+            writeInt(0); // Header size will be filled in later
+            StackTraceElement[] stackTrace = e.getStackTrace();
+            final int truncatedSize = Math.min(stackTrace.length, 5);
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < truncatedSize; i++) {
+                sb.append("\tat ").append(stackTrace[i]);
+            }
+            writeString(sb.toString());
+            final int payloadPosition = dataPosition();
+            setDataPosition(sizePosition);
+            // Write stack trace header size. Used in native side to skip the header
+            writeInt(payloadPosition - sizePosition);
+            setDataPosition(payloadPosition);
+        } else {
+            writeInt(0);
+        }
         switch (code) {
             case EX_SERVICE_SPECIFIC:
                 writeInt(((ServiceSpecificException) e).errorCode);
@@ -1818,7 +1856,19 @@
         int code = readExceptionCode();
         if (code != 0) {
             String msg = readString();
-            readException(code, msg);
+            String remoteStackTrace = null;
+            final int remoteStackPayloadSize = readInt();
+            if (remoteStackPayloadSize > 0) {
+                remoteStackTrace = readString();
+            }
+            Exception e = createException(code, msg);
+            // Attach remote stack trace if availalble
+            if (remoteStackTrace != null) {
+                RemoteException cause = new RemoteException(
+                        "Remote stack trace:\n" + remoteStackTrace, null, false, false);
+                e.initCause(cause);
+            }
+            SneakyThrow.sneakyThrow(e);
         }
     }
 
@@ -1863,32 +1913,41 @@
      * @param msg The exception message.
      */
     public final void readException(int code, String msg) {
+        SneakyThrow.sneakyThrow(createException(code, msg));
+    }
+
+    /**
+     * Creates an exception with the given message.
+     *
+     * @param code Used to determine which exception class to throw.
+     * @param msg The exception message.
+     */
+    private Exception createException(int code, String msg) {
         switch (code) {
             case EX_PARCELABLE:
                 if (readInt() > 0) {
-                    SneakyThrow.sneakyThrow(
-                            (Exception) readParcelable(Parcelable.class.getClassLoader()));
+                    return (Exception) readParcelable(Parcelable.class.getClassLoader());
                 } else {
-                    throw new RuntimeException(msg + " [missing Parcelable]");
+                    return new RuntimeException(msg + " [missing Parcelable]");
                 }
             case EX_SECURITY:
-                throw new SecurityException(msg);
+                return new SecurityException(msg);
             case EX_BAD_PARCELABLE:
-                throw new BadParcelableException(msg);
+                return new BadParcelableException(msg);
             case EX_ILLEGAL_ARGUMENT:
-                throw new IllegalArgumentException(msg);
+                return new IllegalArgumentException(msg);
             case EX_NULL_POINTER:
-                throw new NullPointerException(msg);
+                return new NullPointerException(msg);
             case EX_ILLEGAL_STATE:
-                throw new IllegalStateException(msg);
+                return new IllegalStateException(msg);
             case EX_NETWORK_MAIN_THREAD:
-                throw new NetworkOnMainThreadException();
+                return new NetworkOnMainThreadException();
             case EX_UNSUPPORTED_OPERATION:
-                throw new UnsupportedOperationException(msg);
+                return new UnsupportedOperationException(msg);
             case EX_SERVICE_SPECIFIC:
-                throw new ServiceSpecificException(readInt(), msg);
+                return new ServiceSpecificException(readInt(), msg);
         }
-        throw new RuntimeException("Unknown exception code: " + code
+        return new RuntimeException("Unknown exception code: " + code
                 + " msg " + msg);
     }
 
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
index 6d25fc1..4e8b971 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -30,6 +30,12 @@
         super(message);
     }
 
+    /** @hide */
+    public RemoteException(String message, Throwable cause, boolean enableSuppression,
+            boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
     /** {@hide} */
     public RuntimeException rethrowAsRuntimeException() {
         throw new RuntimeException(this);
diff --git a/core/java/android/util/AndroidException.java b/core/java/android/util/AndroidException.java
index dfe00c9b..1345ddf 100644
--- a/core/java/android/util/AndroidException.java
+++ b/core/java/android/util/AndroidException.java
@@ -34,5 +34,11 @@
     public AndroidException(Exception cause) {
         super(cause);
     }
+
+    /** @hide */
+    protected AndroidException(String message, Throwable cause, boolean enableSuppression,
+            boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
 };
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 74a7bd4a..33f4e34 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -35,6 +35,7 @@
 import android.os.IIncidentManager;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.ServiceManager;
@@ -360,6 +361,9 @@
             // to avoid throwing BadParcelableException.
             BaseBundle.setShouldDefuse(true);
 
+            // Within the system server, when parceling exceptions, include the stack trace
+            Parcel.setStackTraceParceling(true);
+
             // Ensure binder calls into the system always run at foreground priority.
             BinderInternal.disableBackgroundScheduling(true);