Extend AnrController to improve ANR dialog controls

I5431d22bd8faa75e3deab1960a2f721784f98d42 introduced a basic ANR
controller interface to allow services within the system_server
register to delay the ANR dialog during an ANR.

Some limitations of that interface were:
1. No way to cancel the ANR dialog if the normal function of the app
resumed during the delay
2. The AnrController#getAnrDelay method was serving a dual purpose of
returning the delay and showing an interim UI
3. The interim UI might live within an updatable mainline
module, hence forcing the system_server to block (asynchronously) on
the 'getter' for the binder call.

This cl addresses the problems above with the following new
methods on AnrController: onAnrDelayStarted and onAnrDelayCompleted.

These methods provide a clearer interface for the following reasons:
1. onAnrDelayCompleted will return a boolean value to indicate whether
the ANR dialog should still be shown after the delay
2. onAnrDelayStarted is a more explicit API that can be called
asynchronously without blocking to notify external components to show
an interim UI
3. The existing getAnrDelay can be handled completely within the
system_server without blocking on an external binder call

See design (2a) in go/transcoding-anrs for more details

Test: Manual
Bug: 170486601
Change-Id: Idb9190a0e6014ce64bf1412c26f6ae03f97e922d
This commit is contained in:
Zim
2021-02-23 12:16:50 +00:00
parent 59536a1edb
commit 465e3b7115
6 changed files with 118 additions and 20 deletions

View File

@@ -23,7 +23,29 @@ package android.app;
public interface AnrController {
/**
* Returns the delay in milliseconds for an ANR dialog that is about to be shown for
* {@code packageName}.
* {@code packageName} with {@code uid}.
*
* Implementations should only return a positive value if they actually expect the
* {@code packageName} to be delayed due to them.
* If there are multiple controllers registered, the controller with the max delay will
* be selected and will receive an {@link #onAnrDelayStarted} callback at the start of the
* delay and an {@link #onAnrDelayCompleted} at the end of the delay.
*/
long getAnrDelayMillis(String packageName, int uid);
/**
* Notifies the controller at the start of the ANR dialog delay for {@code packageName} with
* {@code uid}. The controller can decide to show a progress UI after this notification.
*/
void onAnrDelayStarted(String packageName, int uid);
/**
* Notifies the controller at the end of the ANR dialog delay for {@code packageName} with
* {@code uid}.
*
* @return whether the ANR dialog should be shown or cancelled. {@code true} if the
* ANR dialog should be shown, {@code false} if it should be cancelled.
*/
boolean onAnrDelayCompleted(String packageName, int uid);
}

View File

@@ -51,6 +51,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AnrController;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.KeyguardManager;
@@ -938,14 +939,29 @@ class StorageManagerService extends IStorageManager.Stub
if (transcodeEnabled) {
LocalServices.getService(ActivityManagerInternal.class)
.registerAnrController((packageName, uid) -> {
try {
return mStorageSessionController.getAnrDelayMillis(packageName, uid);
} catch (ExternalStorageServiceException e) {
Log.e(TAG, "Failed to get ANR delay for " + packageName, e);
return 0;
}
});
.registerAnrController(new ExternalStorageServiceAnrController());
}
}
// TODO(b/170486601): Check transcoding status based on events pushed from the MediaProvider
private class ExternalStorageServiceAnrController implements AnrController {
@Override
public long getAnrDelayMillis(String packageName, int uid) {
int delay = SystemProperties.getInt("sys.fuse.transcode_anr_delay", 0);
Log.d(TAG, "getAnrDelayMillis: " + packageName + ". Delaying for " + delay + "ms");
return delay;
}
@Override
public void onAnrDelayStarted(String packageName, int uid) {
Log.d(TAG, "onAnrDelayStarted: " + packageName);
}
@Override
public boolean onAnrDelayCompleted(String packageName, int uid) {
boolean show = SystemProperties.getBoolean("sys.fuse.transcode_anr_dialog_show", true);
Log.d(TAG, "onAnrDelayCompleted: " + packageName + ". Show: " + show);
return show;
}
}

View File

@@ -29,6 +29,7 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_N
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AnrController;
import android.app.ApplicationErrorReport;
import android.app.ApplicationExitInfo;
import android.content.ActivityNotFoundException;
@@ -1058,7 +1059,26 @@ class AppErrors {
Settings.Secure.ANR_SHOW_BACKGROUND, 0,
mService.mUserController.getCurrentUserId()) != 0;
if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) {
errState.getDialogController().showAnrDialogs(data);
AnrController anrController = errState.getDialogController().getAnrController();
if (anrController == null) {
errState.getDialogController().showAnrDialogs(data);
} else {
String packageName = proc.info.packageName;
int uid = proc.info.uid;
boolean showDialog = anrController.onAnrDelayCompleted(packageName, uid);
if (showDialog) {
Slog.d(TAG, "ANR delay completed. Showing ANR dialog for package: "
+ packageName);
errState.getDialogController().showAnrDialogs(data);
} else {
Slog.d(TAG, "ANR delay completed. Cancelling ANR dialog for package: "
+ packageName);
errState.setNotResponding(false);
errState.setNotRespondingReport(null);
errState.getDialogController().clearAnrDialogs();
}
}
} else {
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
AppNotRespondingDialog.CANT_SHOW);

View File

@@ -16,6 +16,8 @@
package com.android.server.am;
import android.annotation.Nullable;
import android.app.AnrController;
import android.app.Dialog;
import android.content.Context;
@@ -57,6 +59,13 @@ final class ErrorDialogController {
@GuardedBy("mProcLock")
private AppWaitingForDebuggerDialog mWaitDialog;
/**
* ANR dialog controller
*/
@GuardedBy("mProcLock")
@Nullable
private AnrController mAnrController;
@GuardedBy("mProcLock")
boolean hasCrashDialogs() {
return mCrashDialogs != null;
@@ -118,6 +127,7 @@ final class ErrorDialogController {
}
forAllDialogs(mAnrDialogs, Dialog::dismiss);
mAnrDialogs = null;
mAnrController = null;
}
@GuardedBy("mProcLock")
@@ -220,6 +230,17 @@ final class ErrorDialogController {
});
}
@GuardedBy("mProcLock")
@Nullable
AnrController getAnrController() {
return mAnrController;
}
@GuardedBy("mProcLock")
void setAnrController(AnrController controller) {
mAnrController = controller;
}
/**
* Helper function to collect contexts from crashed app located displays.
*

View File

@@ -22,6 +22,7 @@ import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.am.ProcessRecord.TAG;
import android.app.ActivityManager;
import android.app.AnrController;
import android.app.ApplicationErrorReport;
import android.app.ApplicationExitInfo;
import android.content.ComponentName;
@@ -418,10 +419,16 @@ class ProcessErrorStateRecord {
// 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");
AnrController anrController = mService.mActivityTaskManager.getAnrController(aInfo);
long anrDialogDelayMs = 0;
if (anrController != null) {
String packageName = aInfo.packageName;
int uid = aInfo.uid;
anrDialogDelayMs = anrController.getAnrDelayMillis(packageName, uid);
// Might execute an async binder call to a system app to show an interim
// ANR progress UI
anrController.onAnrDelayStarted(packageName, uid);
Slog.i(TAG, "ANR delay of " + anrDialogDelayMs + "ms started for " + packageName);
}
synchronized (mService) {
@@ -440,6 +447,7 @@ class ProcessErrorStateRecord {
// Set the app's notResponding state, and look up the errorReportReceiver
makeAppNotRespondingLSP(activityShortComponentName,
annotation != null ? "ANR " + annotation : "ANR", info.toString());
mDialogController.setAnrController(anrController);
}
// Notify package manager service to possibly update package state

View File

@@ -2095,10 +2095,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
/** @return the max ANR delay from all registered {@link AnrController} instances */
public long getMaxAnrDelayMillis(ApplicationInfo info) {
/**
* @return the controller with the max ANR delay from all registered
* {@link AnrController} instances
*/
@Nullable
public AnrController getAnrController(ApplicationInfo info) {
if (info == null || info.packageName == null) {
return 0;
return null;
}
final ArrayList<AnrController> controllers;
@@ -2107,12 +2111,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
final String packageName = info.packageName;
final int uid = info.uid;
long maxDelayMs = 0;
AnrController controllerWithMaxDelay = null;
for (AnrController controller : controllers) {
maxDelayMs = Math.max(maxDelayMs, controller.getAnrDelayMillis(packageName, info.uid));
long delayMs = controller.getAnrDelayMillis(packageName, uid);
if (delayMs > 0 && delayMs > maxDelayMs) {
controllerWithMaxDelay = controller;
maxDelayMs = delayMs;
}
}
maxDelayMs = Math.max(maxDelayMs, 0);
return maxDelayMs;
return controllerWithMaxDelay;
}
@Override