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

Commit ee271a70 authored by Varun Shah's avatar Varun Shah
Browse files

Introduce a new flag to enable crashing the app on FGS timeout.

Decouple what happens to an app after one of its foreground services
times out. With this new flag enabled, instead of ANRing the app, crash
the app.

Bug: 339526947
Test: atest CtsFgsTimeoutTestCases
Flag: android.app.enable_fgs_timeout_crash_behavior
Change-Id: I8270f50294af17d8bff1ab00ddb33b88d0a68283
parent b524e253
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.app.RemoteServiceException.BadUserInitiatedJobNotificationExcepti
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
import android.app.RemoteServiceException.CrashedByAdbException;
import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException;
import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
@@ -2236,6 +2237,9 @@ public final class ActivityThread extends ClientTransactionHandler
            case ForegroundServiceDidNotStartInTimeException.TYPE_ID:
                throw generateForegroundServiceDidNotStartInTimeException(message, extras);

            case ForegroundServiceDidNotStopInTimeException.TYPE_ID:
                throw generateForegroundServiceDidNotStopInTimeException(message, extras);

            case CannotPostForegroundServiceNotificationException.TYPE_ID:
                throw new CannotPostForegroundServiceNotificationException(message);

@@ -2266,6 +2270,15 @@ public final class ActivityThread extends ClientTransactionHandler
        throw new ForegroundServiceDidNotStartInTimeException(message, inner);
    }

    private ForegroundServiceDidNotStopInTimeException
            generateForegroundServiceDidNotStopInTimeException(String message, Bundle extras) {
        final String serviceClassName =
                ForegroundServiceDidNotStopInTimeException.getServiceClassNameFromExtras(extras);
        final Exception inner = (serviceClassName == null) ? null
                : Service.getStartForegroundServiceStackTrace(serviceClassName);
        throw new ForegroundServiceDidNotStopInTimeException(message, inner);
    }

    class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        @UnsupportedAppUsage
+27 −0
Original line number Diff line number Diff line
@@ -70,6 +70,33 @@ public class RemoteServiceException extends AndroidRuntimeException {
        }
    }

    /**
     * Exception used to crash an app process when it didn't stop after hitting its time limit.
     *
     * @hide
     */
    public static class ForegroundServiceDidNotStopInTimeException extends RemoteServiceException {
        /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
        public static final int TYPE_ID = 7;

        private static final String KEY_SERVICE_CLASS_NAME = "serviceclassname";

        public ForegroundServiceDidNotStopInTimeException(String msg, Throwable cause) {
            super(msg, cause);
        }

        public static Bundle createExtrasForService(@NonNull ComponentName service) {
            Bundle b = new Bundle();
            b.putString(KEY_SERVICE_CLASS_NAME, service.getClassName());
            return b;
        }

        @Nullable
        public static String getServiceClassNameFromExtras(@Nullable Bundle extras) {
            return (extras == null) ? null : extras.getString(KEY_SERVICE_CLASS_NAME);
        }
    }

    /**
     * Exception used to crash an app process when the system received a RemoteException
     * while posting a notification of a foreground service.
+1 −2
Original line number Diff line number Diff line
@@ -1198,8 +1198,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
     * Callback called when a particular foreground service type has timed out.
     *
     * <p>This callback is meant to give the app a small grace period of a few seconds to finish
     * the foreground service of the associated type - if it fails to do so, the app will be
     * declared an ANR.
     * the foreground service of the associated type - if it fails to do so, the app will crash.
     *
     * <p>The foreground service of the associated type can be stopped within the time limit by
     * {@link android.app.Service#stopSelf()},
+10 −0
Original line number Diff line number Diff line
@@ -60,3 +60,13 @@ flag {
         purpose: PURPOSE_BUGFIX
     }
}

flag {
     namespace: "backstage_power"
     name: "enable_fgs_timeout_crash_behavior"
     description: "Enable the new behavior where the app is crashed once an FGS times out."
     bug: "339526947"
     metadata {
         purpose: PURPOSE_BUGFIX
     }
}
+38 −19
Original line number Diff line number Diff line
@@ -157,6 +157,7 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException;
import android.app.Service;
import android.app.ServiceStartArgs;
import android.app.StartForegroundCalledOnStoppedServiceException;
@@ -784,7 +785,7 @@ public final class ActiveServices {
                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
                "SERVICE_FOREGROUND_TIMEOUT");
        this.mFGSAnrTimer = new ServiceAnrTimer(service,
                ActivityManagerService.SERVICE_FGS_ANR_TIMEOUT_MSG,
                ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG,
                "FGS_TIMEOUT");
    }

@@ -2458,12 +2459,14 @@ public final class ActiveServices {
                                            + " foreground service type "
                                            + ServiceInfo.foregroundServiceTypeToLabel(
                                                    foregroundServiceType);
                                    if (!android.app.Flags.gateFgsTimeoutAnrBehavior()) {
                                    // Only throw an exception if the new ANR behavior
                                    // ("do nothing") is not gated or the new crashing logic gate
                                    // is enabled; otherwise, reset the limit temporarily.
                                    if (!android.app.Flags.gateFgsTimeoutAnrBehavior()
                                            || android.app.Flags.enableFgsTimeoutCrashBehavior()) {
                                        throw new ForegroundServiceStartNotAllowedException(
                                                    exceptionMsg);
                                    } else {
                                        // Only throw an exception above while the new ANR behavior
                                        // is not gated, otherwise, reset the limit temporarily.
                                        Slog.wtf(TAG, exceptionMsg);
                                        fgsTypeInfo.reset();
                                    }
@@ -3938,12 +3941,12 @@ public final class ActiveServices {
                Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e);
            }

            // ANR the service after giving the service some time to clean up.
            mFGSAnrTimer.start(sr, mAm.mConstants.mFgsAnrExtraWaitDuration);
            // Crash the service after giving the service some time to clean up.
            mFGSAnrTimer.start(sr, mAm.mConstants.mFgsCrashExtraWaitDuration);
        }
    }

    void onFgsAnrTimeout(ServiceRecord sr) {
    void onFgsCrashTimeout(ServiceRecord sr) {
        final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType);
        if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) {
            return; // no timed out FGS type was found (either it was stopped or it switched types)
@@ -3958,6 +3961,21 @@ public final class ActiveServices {
            return;
        }

        if (android.app.Flags.enableFgsTimeoutCrashBehavior()) {
            // Crash the app
            synchronized (mAm) {
                Slog.e(TAG_SERVICE, "FGS Crashed: " + sr);
                traceInstant("FGS Crash: ", sr);
                if (sr.app != null) {
                    mAm.crashApplicationWithTypeWithExtras(sr.app.uid, sr.app.getPid(),
                            sr.app.info.packageName, sr.app.userId, reason, false /*force*/,
                            ForegroundServiceDidNotStopInTimeException.TYPE_ID,
                            ForegroundServiceDidNotStopInTimeException
                                    .createExtrasForService(sr.getComponentName()));
                }
            }
        } else {
            // ANR the app if the new crash behavior is not enabled
            final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
            tr.mLatencyTracker.waitingOnAMSLockStarted();
            synchronized (mAm) {
@@ -3970,8 +3988,9 @@ public final class ActiveServices {
                }

                // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
            // dialog really doesn't remember the "cause" (especially if there have been multiple
            // ANRs), so it's not doable.
                // dialog really doesn't remember the "cause" (especially if there have been
                // multiple ANRs), so it's not doable.
            }
        }
    }

Loading