Don't throw TransactionTooLargeException for small payloads

In practice, if we ever see an apparent transaction-too-large result
with a modest-sized payload, it means that the remote process died at
just the right time (with the binder transaction already in flight
so it wasn't detected as a DEAD_OBJECT up front).  Don't throw
TransactionTooLargeException in this case, because we really do need
to distinguish that from dead-remote more accurately.

In particular, certain common execution patterns on existing hardware
trigger this circumstance, and they wind up crashing the system.
This is bad, so now we avoid it unless we're pretty sure that is
really what happened.

Bug 21801759

Change-Id: Id05f1eecc0d23dc8d0505c402e2cb68396782135
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 70a7805..e2cfa44 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -682,18 +682,28 @@
             break;
         case FAILED_TRANSACTION: {
             ALOGE("!!! FAILED BINDER TRANSACTION !!!  (parcel size = %d)", parcelSize);
+            const char* exceptionToThrow;
             char msg[128];
-            snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
             // TransactionTooLargeException is a checked exception, only throw from certain methods.
             // FIXME: Transaction too large is the most common reason for FAILED_TRANSACTION
             //        but it is not the only one.  The Binder driver can return BR_FAILED_REPLY
             //        for other reasons also, such as if the transaction is malformed or
             //        refers to an FD that has been closed.  We should change the driver
             //        to enable us to distinguish these cases in the future.
-            jniThrowException(env, canThrowRemoteException
-                    ? "android/os/TransactionTooLargeException"
-                            : "java/lang/RuntimeException",
-                    parcelSize > 0 ? msg : NULL);
+            if (canThrowRemoteException && parcelSize > 200*1024) {
+                // bona fide large payload
+                exceptionToThrow = "android/os/TransactionTooLargeException";
+                snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
+            } else {
+                // Heuristic: a payload smaller than this threshold "shouldn't" be too
+                // big, so it's probably some other, more subtle problem.  In practice
+                // it nearly always means that the remote process died while the binder
+                // transaction was already in flight.
+                exceptionToThrow = "java/lang/RuntimeException";
+                snprintf(msg, sizeof(msg)-1,
+                        "Transaction failed on small parcel; remote process probably died");
+            }
+            jniThrowException(env, exceptionToThrow, msg);
         } break;
         case FDS_NOT_ALLOWED:
             jniThrowException(env, "java/lang/RuntimeException",