Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 465e3b71 authored by Zim's avatar Zim
Browse files

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
parent 59536a1e
Loading
Loading
Loading
Loading
+23 −1
Original line number Diff line number Diff line
@@ -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);
}
+24 −8
Original line number Diff line number Diff line
@@ -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;
        }
    }

+21 −1
Original line number Diff line number Diff line
@@ -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) {
                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);
+21 −0
Original line number Diff line number Diff line
@@ -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.
     *
+12 −4
Original line number Diff line number Diff line
@@ -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
Loading