diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 9b6f4b43581bc..c31c22cca329a 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -538,4 +538,10 @@ public abstract class ActivityManagerInternal { * @return mBootTimeTempAllowlistDuration of ActivityManagerConstants. */ public abstract long getBootTimeTempAllowListDuration(); + + /** Register an {@link AnrController} to control the ANR dialog behavior */ + public abstract void registerAnrController(AnrController controller); + + /** Unregister an {@link AnrController} */ + public abstract void unregisterAnrController(AnrController controller); } diff --git a/core/java/android/app/AnrController.java b/core/java/android/app/AnrController.java new file mode 100644 index 0000000000000..cfc9d27157200 --- /dev/null +++ b/core/java/android/app/AnrController.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 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; + +/** + * Interface to control the ANR dialog within the activity manager + * {@hide} + */ +public interface AnrController { + /** + * Returns the delay in milliseconds for an ANR dialog that is about to be shown for + * {@code packageName}. + */ + long getAnrDelayMillis(String packageName, int uid); +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6ae064435da3f..9382e1aa2a9e3 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -145,6 +145,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.ActivityThread; +import android.app.AnrController; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AppOpsManagerInternal.CheckOpsDelegate; @@ -15861,6 +15862,16 @@ public class ActivityManagerService extends IActivityManager.Stub // PackageManagerService. return mConstants.mBootTimeTempAllowlistDuration; } + + @Override + public void registerAnrController(AnrController controller) { + mActivityTaskManager.registerAnrController(controller); + } + + @Override + public void unregisterAnrController(AnrController controller) { + mActivityTaskManager.unregisterAnrController(controller); + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 64e307a5f1820..165312352990a 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -416,6 +416,14 @@ class ProcessErrorStateRecord { return; } + // Retrieve max ANR delay from AnrControllers without the mService lock since the + // controllers might in turn call into apps + long anrDialogDelayMs = mService.mActivityTaskManager.getMaxAnrDelayMillis(aInfo); + if (aInfo != null && aInfo.packageName != null && anrDialogDelayMs > 0) { + Slog.i(TAG, "Delaying ANR dialog for " + aInfo.packageName + " for " + anrDialogDelayMs + + "ms"); + } + synchronized (mService) { // mBatteryStatsService can be null if the AMS is constructed with injector only. This // will only happen in tests. @@ -447,7 +455,7 @@ class ProcessErrorStateRecord { msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG; msg.obj = new AppNotRespondingDialog.Data(mApp, aInfo, aboveSystem); - mService.mUiHandler.sendMessage(msg); + mService.mUiHandler.sendMessageDelayed(msg, anrDialogDelayMs); } } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 2d6e9b2c68c52..4bcef408d9a0b 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -130,6 +130,7 @@ import android.app.ActivityTaskManager; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.ActivityThread; import android.app.AlertDialog; +import android.app.AnrController; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.Dialog; @@ -494,6 +495,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ private volatile long mLastStopAppSwitchesTime; + private final List mAnrController = new ArrayList<>(); IActivityController mController = null; boolean mControllerIsAMonkey = false; @@ -2058,6 +2060,40 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mAppSwitchesAllowed; } + /** Register an {@link AnrController} to control the ANR dialog behavior */ + public void registerAnrController(AnrController controller) { + synchronized (mGlobalLock) { + mAnrController.add(controller); + } + } + + /** Unregister an {@link AnrController} */ + public void unregisterAnrController(AnrController controller) { + synchronized (mGlobalLock) { + mAnrController.remove(controller); + } + } + + /** @return the max ANR delay from all registered {@link AnrController} instances */ + public long getMaxAnrDelayMillis(ApplicationInfo info) { + if (info == null || info.packageName == null) { + return 0; + } + + final ArrayList controllers; + synchronized (mGlobalLock) { + controllers = new ArrayList<>(mAnrController); + } + + final String packageName = info.packageName; + long maxDelayMs = 0; + for (AnrController controller : controllers) { + maxDelayMs = Math.max(maxDelayMs, controller.getAnrDelayMillis(packageName, info.uid)); + } + maxDelayMs = Math.max(maxDelayMs, 0); + return maxDelayMs; + } + @Override public void setActivityController(IActivityController controller, boolean imAMonkey) { mAmInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,