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

Commit f88dcd30 authored by Kweku Adams's avatar Kweku Adams
Browse files

Trigger an ANR for slow app responses.

When an app doesn't respond quickly enough to onStartJob or onStopJob,
JobScheduler stops tracking the job internally without giving any
indication to the app that the job is no longer considered operational.
Now, we trigger an ANR in these situations for apps targeting U+ so they
can get some signal (at least via analytics).

Bug: 258236856
Test: atest CtsJobSchedulerTestCases:JobSchedulingTest
Change-Id: I6a4266f779c553d0c60a75bd95280352531476f7
parent db63d9a6
Loading
Loading
Loading
Loading
+52 −9
Original line number Diff line number Diff line
@@ -24,7 +24,9 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.app.Notification;
import android.app.compat.CompatChanges;
import android.app.job.IJobCallback;
import android.app.job.IJobService;
import android.app.job.JobInfo;
@@ -32,6 +34,9 @@ import android.app.job.JobParameters;
import android.app.job.JobProtoEnums;
import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManagerInternal;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledAfter;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -57,6 +62,7 @@ import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
@@ -87,6 +93,15 @@ public final class JobServiceContext implements ServiceConnection {
    private static final boolean DEBUG = JobSchedulerService.DEBUG;
    private static final boolean DEBUG_STANDBY = JobSchedulerService.DEBUG_STANDBY;

    /**
     * Whether to trigger an ANR when apps are slow to respond on pre-UDC APIs and functionality.
     */
    @ChangeId
    @Disabled
    // TODO(258236856): Enable after test is fixed
    // @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
    private static final long ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES = 258236856L;

    private static final String TAG = "JobServiceContext";
    /** Amount of time the JobScheduler waits for the initial service launch+bind. */
    private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
@@ -119,6 +134,7 @@ public final class JobServiceContext implements ServiceConnection {
    /** Used for service binding, etc. */
    private final Context mContext;
    private final Object mLock;
    private final ActivityManagerInternal mActivityManagerInternal;
    private final IBatteryStats mBatteryStats;
    private final EconomyManagerInternal mEconomyManagerInternal;
    private final JobPackageTracker mJobPackageTracker;
@@ -270,6 +286,7 @@ public final class JobServiceContext implements ServiceConnection {
        mContext = service.getContext();
        mLock = service.getLock();
        mService = service;
        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
        mBatteryStats = batteryStats;
        mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class);
        mJobPackageTracker = tracker;
@@ -1121,23 +1138,31 @@ public final class JobServiceContext implements ServiceConnection {
    private void handleOpTimeoutLocked() {
        switch (mVerb) {
            case VERB_BINDING:
                Slog.w(TAG, "Time-out while trying to bind " + getRunningJobNameLocked()
                        + ", dropping.");
                closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding");
                onSlowAppResponseLocked(/* reschedule */ false, /* updateStopReasons */ true,
                        /* debugReason */ "timed out while binding",
                        /* anrMessage */ "Timed out while trying to bind",
                        CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
                            mRunningJob.getUid()));
                break;
            case VERB_STARTING:
                // Client unresponsive - wedged or failed to respond in time. We don't really
                // know what happened so let's log it and notify the JobScheduler
                // FINISHED/NO-RETRY.
                Slog.w(TAG, "No response from client for onStartJob "
                        + getRunningJobNameLocked());
                closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting");
                onSlowAppResponseLocked(/* reschedule */ false, /* updateStopReasons */ true,
                        /* debugReason */ "timed out while starting",
                        /* anrMessage */ "No response to onStartJob",
                        CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
                            mRunningJob.getUid()));
                break;
            case VERB_STOPPING:
                // At least we got somewhere, so fail but ask the JobScheduler to reschedule.
                Slog.w(TAG, "No response from client for onStopJob "
                        + getRunningJobNameLocked());
                closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
                // Don't update the stop reasons since we were already stopping the job for some
                // other reason.
                onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ false,
                        /* debugReason */ "timed out while stopping",
                        /* anrMessage */ "No response to onStopJob",
                        CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
                            mRunningJob.getUid()));
                break;
            case VERB_EXECUTING:
                if (mPendingStopReason != JobParameters.STOP_REASON_UNDEFINED) {
@@ -1218,6 +1243,24 @@ public final class JobServiceContext implements ServiceConnection {
        }
    }

    @GuardedBy("mLock")
    private void onSlowAppResponseLocked(boolean reschedule, boolean updateStopReasons,
            @NonNull String debugReason, @NonNull String anrMessage, boolean triggerAnr) {
        Slog.w(TAG, anrMessage + " for " + getRunningJobNameLocked());
        if (updateStopReasons) {
            mParams.setStopReason(
                    JobParameters.STOP_REASON_UNDEFINED,
                    JobParameters.INTERNAL_STOP_REASON_ANR,
                    debugReason);
        }
        if (triggerAnr) {
            mActivityManagerInternal.appNotResponding(
                    mRunningJob.serviceProcessName, mRunningJob.getUid(),
                    TimeoutRecord.forJobService(anrMessage));
        }
        closeAndCleanupJobLocked(reschedule, debugReason);
    }

    /**
     * The provided job has finished, either by calling
     * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
+6 −0
Original line number Diff line number Diff line
@@ -503,6 +503,12 @@ public abstract class ActivityManagerInternal {
     */
    public abstract void broadcastCloseSystemDialogs(String reason);

    /**
     * Trigger an ANR for the specified process.
     */
    public abstract void appNotResponding(@NonNull String processName, int uid,
            @NonNull TimeoutRecord timeoutRecord);

    /**
     * Kills all background processes, except those matching any of the specified properties.
     *
+10 −1
Original line number Diff line number Diff line
@@ -41,7 +41,9 @@ public class TimeoutRecord {
            TimeoutKind.SERVICE_EXEC,
            TimeoutKind.CONTENT_PROVIDER,
            TimeoutKind.APP_REGISTERED,
            TimeoutKind.SHORT_FGS_TIMEOUT})
            TimeoutKind.SHORT_FGS_TIMEOUT,
            TimeoutKind.JOB_SERVICE,
    })

    @Retention(RetentionPolicy.SOURCE)
    public @interface TimeoutKind {
@@ -53,6 +55,7 @@ public class TimeoutRecord {
        int CONTENT_PROVIDER = 6;
        int APP_REGISTERED = 7;
        int SHORT_FGS_TIMEOUT = 8;
        int JOB_SERVICE = 9;
    }

    /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -152,4 +155,10 @@ public class TimeoutRecord {
    public static TimeoutRecord forShortFgsTimeout(String reason) {
        return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason);
    }

    /** Record for a job related timeout. */
    @NonNull
    public static TimeoutRecord forJobService(String reason) {
        return TimeoutRecord.endingNow(TimeoutKind.JOB_SERVICE, reason);
    }
}
+21 −0
Original line number Diff line number Diff line
@@ -6770,6 +6770,21 @@ public class ActivityManagerService extends IActivityManager.Stub
        mAnrHelper.appNotResponding(anrProcess, timeoutRecord);
    }
    private void appNotResponding(@NonNull String processName, int uid,
            @NonNull TimeoutRecord timeoutRecord) {
        Objects.requireNonNull(processName);
        Objects.requireNonNull(timeoutRecord);
        synchronized (this) {
            final ProcessRecord app = getProcessRecordLocked(processName, uid);
            if (app == null) {
                Slog.e(TAG, "Unknown process: " + processName);
                return;
            }
            mAnrHelper.appNotResponding(app, timeoutRecord);
        }
    }
    void startPersistentApps(int matchFlags) {
        if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) return;
@@ -17926,6 +17941,12 @@ public class ActivityManagerService extends IActivityManager.Stub
            }
        }
        @Override
        public void appNotResponding(@NonNull String processName, int uid,
                @NonNull TimeoutRecord timeoutRecord) {
            ActivityManagerService.this.appNotResponding(processName, uid, timeoutRecord);
        }
        @Override
        public void killAllBackgroundProcessesExcept(int minTargetSdk, int maxProcState) {
            synchronized (ActivityManagerService.this) {