Merge "Add logic to re-raise ANR dialogs if the user taps wait."
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7145c0f..b658204 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -412,6 +412,13 @@
     public abstract void inputDispatchingResumed(int pid);
 
     /**
+     * User tapped "wait" in the ANR dialog - reschedule the dialog to be shown again at a later
+     * time.
+     * @param data AppNotRespondingDialog.Data object
+     */
+    public abstract void rescheduleAnrDialog(Object data);
+
+    /**
      * Sends {@link android.content.Intent#ACTION_CONFIGURATION_CHANGED} with all the appropriate
      * flags.
      */
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b3d048d..cdfc795 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -257,6 +257,7 @@
 import android.os.IPermissionController;
 import android.os.IProcessInfoService;
 import android.os.IProgressListener;
+import android.os.InputConstants;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
@@ -15992,8 +15993,22 @@
 
         @Override
         public void inputDispatchingResumed(int pid) {
-            // TODO (b/171218828)
-            return;
+            final ProcessRecord proc;
+            synchronized (mPidsSelfLocked) {
+                proc = mPidsSelfLocked.get(pid);
+            }
+            if (proc != null) {
+                mAppErrors.handleDismissAnrDialogs(proc);
+            }
+        }
+
+        @Override
+        public void rescheduleAnrDialog(Object data) {
+            Message msg = Message.obtain();
+            msg.what = SHOW_NOT_RESPONDING_UI_MSG;
+            msg.obj = (AppNotRespondingDialog.Data) data;
+
+            mUiHandler.sendMessageDelayed(msg, InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index bcb42bb..0dfdfe9 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -1058,6 +1058,7 @@
         }
         synchronized (mProcLock) {
             final ProcessErrorStateRecord errState = proc.mErrorState;
+            errState.setAnrData(data);
             if (!proc.isPersistent()) {
                 packageList = proc.getPackageListWithVersionCode();
             }
@@ -1109,6 +1110,24 @@
         }
     }
 
+    void handleDismissAnrDialogs(ProcessRecord proc) {
+        synchronized (mProcLock) {
+            final ProcessErrorStateRecord errState = proc.mErrorState;
+
+            // Cancel any rescheduled ANR dialogs
+            mService.mUiHandler.removeMessages(
+                    ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG, errState.getAnrData());
+
+            // Dismiss any ANR dialogs currently visible
+            if (errState.getDialogController().hasAnrDialogs()) {
+                errState.setNotResponding(false);
+                errState.setNotRespondingReport(null);
+                errState.getDialogController().clearAnrDialogs();
+            }
+            proc.mErrorState.setAnrData(null);
+        }
+    }
+
     /**
      * Information about a process that is currently marked as bad.
      */
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
index b233a2c..878ef31 100644
--- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -48,12 +48,14 @@
 
     private final ActivityManagerService mService;
     private final ProcessRecord mProc;
+    private final Data mData;
 
     public AppNotRespondingDialog(ActivityManagerService service, Context context, Data data) {
         super(context);
 
         mService = service;
         mProc = data.proc;
+        mData = data;
         Resources res = context.getResources();
 
         setCancelable(false);
@@ -165,6 +167,8 @@
                             errState.getDialogController().clearAnrDialogs();
                         }
                         mService.mServices.scheduleServiceTimeoutLocked(app);
+                        // If the app remains unresponsive, show the dialog again after a delay.
+                        mService.mInternal.rescheduleAnrDialog(mData);
                     }
                     break;
             }
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 7e79ef5..8d3e442 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -117,6 +117,12 @@
     private ComponentName mErrorReportReceiver;
 
     /**
+     * ANR dialog data used to dismiss any visible ANR dialogs if the app becomes responsive.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private AppNotRespondingDialog.Data mAnrData;
+
+    /**
      * Optional local handler to be invoked in the process crash.
      */
     @CompositeRWLock({"mService", "mProcLock"})
@@ -209,6 +215,16 @@
         return mDialogController;
     }
 
+    @GuardedBy({"mService", "mProcLock"})
+    void setAnrData(AppNotRespondingDialog.Data data) {
+        mAnrData = data;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    AppNotRespondingDialog.Data getAnrData() {
+        return mAnrData;
+    }
+
     ProcessErrorStateRecord(ProcessRecord app) {
         mApp = app;
         mService = app.mService;
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 4da3eca..3eeba7d 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -70,7 +70,45 @@
     }
 
     @Test
-    fun testGestureMonitorAnr() {
+    fun testGestureMonitorAnr_Close() {
+        triggerAnr()
+        clickCloseAppOnAnrDialog()
+    }
+
+    @Test
+    fun testGestureMonitorAnr_Wait() {
+        triggerAnr()
+        clickWaitOnAnrDialog()
+        SystemClock.sleep(500) // Wait at least 500ms after tapping on wait
+        // ANR dialog should reappear after a delay - find the close button on it to verify
+        clickCloseAppOnAnrDialog()
+    }
+
+    private fun clickCloseAppOnAnrDialog() {
+        // Find anr dialog and kill app
+        val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
+        val closeAppButton: UiObject2? =
+                uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
+        if (closeAppButton == null) {
+            fail("Could not find anr dialog")
+            return
+        }
+        closeAppButton.click()
+    }
+
+    private fun clickWaitOnAnrDialog() {
+        // Find anr dialog and tap on wait
+        val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
+        val waitButton: UiObject2? =
+                uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
+        if (waitButton == null) {
+            fail("Could not find anr dialog/wait button")
+            return
+        }
+        waitButton.click()
+    }
+
+    private fun triggerAnr() {
         startUnresponsiveActivity()
         val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
         val obj: UiObject2? = uiDevice.wait(Until.findObject(
@@ -91,20 +129,6 @@
 
         // Todo: replace using timeout from android.hardware.input.IInputManager
         SystemClock.sleep(5000) // default ANR timeout for gesture monitors
-
-        clickCloseAppOnAnrDialog()
-    }
-
-    private fun clickCloseAppOnAnrDialog() {
-        // Find anr dialog and kill app
-        val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation)
-        val closeAppButton: UiObject2? =
-                uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
-        if (closeAppButton == null) {
-            fail("Could not find anr dialog")
-            return
-        }
-        closeAppButton.click()
     }
 
     private fun startUnresponsiveActivity() {