ActivityManager: kill frozen processes receiving sync transactions

Synchronous transactions to frozen processes are rejected by binder,
which can potentially leave these processes in an unclean state when
unfrozen. Kill such processes insted of unfreezing then when they're
removed from the cache. This patch also increases the timeout before
freezing an app, to allow pending transactions to be processes for
a longer window after caching and so to reduce the amount of background
kills.

Bug: 143717177
Test: cached/frozen a few apps, sent sync and async transactions to them
and verified that killing happened when expected.

Change-Id: Ieca9d49e1befd1358862aa7afeeec15a408d7ef3
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 9660389..8112bb8 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -22,6 +22,7 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityThread;
+import android.app.ApplicationExitInfo;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.Message;
@@ -132,11 +133,15 @@
     static final int REPORT_UNFREEZE_MSG = 4;
 
     //TODO:change this static definition into a configurable flag.
-    static final int FREEZE_TIMEOUT_MS = 500;
+    static final int FREEZE_TIMEOUT_MS = 10000;
 
     static final int DO_FREEZE = 1;
     static final int REPORT_UNFREEZE = 2;
 
+    // Bitfield values for sync/async transactions reveived by frozen processes
+    static final int SYNC_RECEIVED_WHILE_FROZEN = 1;
+    static final int ASYNC_RECEIVED_WHILE_FROZEN = 2;
+
     /**
      * This thread must be moved to the system background cpuset.
      * If that doesn't happen, it's probably going to draw a lot of power.
@@ -494,6 +499,15 @@
     private static native void freezeBinder(int pid, boolean freeze);
 
     /**
+     * Retrieves binder freeze info about a process.
+     * @param pid the pid for which binder freeze info is to be retrieved.
+     *
+     * @throws RuntimeException if the operation could not complete successfully.
+     * @return a bit field reporting the binder freeze info for the process.
+     */
+    private static native int getBinderFreezeInfo(int pid);
+
+    /**
      * Determines whether the freezer is supported by this system
      */
     public static boolean isFreezerSupported() {
@@ -729,6 +743,37 @@
             return;
         }
 
+        boolean processKilled = false;
+
+        try {
+            int freezeInfo = getBinderFreezeInfo(app.pid);
+
+            if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
+                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
+                        + " received sync transactions while frozen, killing");
+                app.kill("Sync transaction while in frozen state",
+                        ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+                processKilled = true;
+            }
+
+            if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
+                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
+                        + " received async transactions while frozen");
+            }
+        } catch (Exception e) {
+            Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + app.pid + " "
+                    + app.processName + ". Killing it. Exception: " + e);
+            app.kill("Unable to query binder frozen stats",
+                    ApplicationExitInfo.REASON_OTHER,
+                    ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+            processKilled = true;
+        }
+
+        if (processKilled) {
+            return;
+        }
+
         long freezeTime = app.freezeUnfreezeTime;
 
         try {
@@ -745,8 +790,12 @@
             try {
                 freezeBinder(app.pid, false);
             } catch (RuntimeException e) {
-                // TODO: it might be preferable to kill the target pid in this case
-                Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName);
+                Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName
+                        + ". Killing it");
+                app.kill("Unable to unfreeze",
+                        ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+                return;
             }
 
             if (DEBUG_FREEZER) {
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 95d4ba7..678308a 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -36,6 +36,9 @@
 using android::base::StringPrintf;
 using android::base::WriteStringToFile;
 
+#define SYNC_RECEIVED_WHILE_FROZEN (1)
+#define ASYNC_RECEIVED_WHILE_FROZEN (2)
+
 namespace android {
 
 // This performs per-process reclaim on all processes belonging to non-app UIDs.
@@ -99,12 +102,37 @@
     }
 }
 
+static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env,
+        jobject clazz, jint pid) {
+    bool syncReceived = false, asyncReceived = false;
+
+    int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
+
+    if (error < 0) {
+        jniThrowException(env, "java/lang/RuntimeException", strerror(error));
+    }
+
+    jint retVal = 0;
+
+    if(syncReceived) {
+        retVal |= SYNC_RECEIVED_WHILE_FROZEN;;
+    }
+
+    if(asyncReceived) {
+        retVal |= ASYNC_RECEIVED_WHILE_FROZEN;
+    }
+
+    return retVal;
+}
+
 static const JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
     {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
     {"enableFreezerInternal", "(Z)V",
         (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal},
-    {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder}
+    {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+    {"getBinderFreezeInfo", "(I)I",
+        (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}
 };
 
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env)