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

Commit 6f17e8dc authored by Sanath Kumar's avatar Sanath Kumar Committed by Android (Google) Code Review
Browse files

Merge changes from topic "empty_jobs_stop_reason" into main

* changes:
  Add new stop reason for maybe abandoned jobs
  Rename empty to abandoned Jobs
parents eec846d9 0034ddd4
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -25,10 +25,10 @@ flag {
}

flag {
   name: "cleanup_empty_jobs"
   name: "handle_abandoned_jobs"
   namespace: "backstage_power"
   description: "Enables automatic cancellation of jobs due to leaked JobParameters, reducing unnecessary battery drain and improving system efficiency. This includes logging and traces for better issue diagnosis."
   bug: "349688611"
   description: "Detect, report and take action on jobs that maybe abandoned by the app without calling jobFinished."
   bug: "372529068"
}

flag {
+4 −3
Original line number Diff line number Diff line
@@ -87,11 +87,12 @@ interface IJobCallback {
    void jobFinished(int jobId, boolean reschedule);

    /*
     * Inform JobScheduler to force finish this job because the client has lost
     * the job handle. jobFinished can no longer be called from the client.
     * Inform JobScheduler that this job may have been abandoned because the client process
     * has lost strong references to the JobParameters object without calling jobFinished.
     *
     * @param jobId Unique integer used to identify this job
     */
    void forceJobFinished(int jobId);
    void handleAbandonedJob(int jobId);

    /*
     * Inform JobScheduler of a change in the estimated transfer payload.
+40 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.app.job;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,8 +34,8 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.Process;
import android.os.RemoteException;
import android.system.SystemCleaner;
import android.util.Log;

@@ -120,6 +121,15 @@ public class JobParameters implements Parcelable {
    public static final int INTERNAL_STOP_REASON_ANR =
            JobProtoEnums.INTERNAL_STOP_REASON_ANR; // 12.

    /**
     * The job ran for at least its minimum execution limit and the app lost the strong reference
     * to the {@link JobParameters}. This could indicate that the job is empty because the app
     * can no longer call {@link JobService#jobFinished(JobParameters, boolean)}.
     * @hide
     */
    public static final int INTERNAL_STOP_REASON_TIMEOUT_ABANDONED =
            JobProtoEnums.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED; // 13.

    /**
     * All the stop reason codes. This should be regarded as an immutable array at runtime.
     *
@@ -144,6 +154,7 @@ public class JobParameters implements Parcelable {
            INTERNAL_STOP_REASON_SUCCESSFUL_FINISH,
            INTERNAL_STOP_REASON_USER_UI_STOP,
            INTERNAL_STOP_REASON_ANR,
            INTERNAL_STOP_REASON_TIMEOUT_ABANDONED,
    };

    /**
@@ -166,6 +177,7 @@ public class JobParameters implements Parcelable {
            case INTERNAL_STOP_REASON_SUCCESSFUL_FINISH: return "successful_finish";
            case INTERNAL_STOP_REASON_USER_UI_STOP: return "user_ui_stop";
            case INTERNAL_STOP_REASON_ANR: return "anr";
            case INTERNAL_STOP_REASON_TIMEOUT_ABANDONED: return "timeout_abandoned";
            default: return "unknown:" + reasonCode;
        }
    }
@@ -269,6 +281,25 @@ public class JobParameters implements Parcelable {
     */
    public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15;

    /**
     * The job used up its maximum execution time and timed out. The system also detected that the
     * app can no longer call {@link JobService#jobFinished(JobParameters, boolean)} for this job,
     * likely because the strong reference to the job handle ({@link JobParameters}) passed to it
     * via {@link JobService#onStartJob(JobParameters)} was lost. This can occur even if the app
     * called {@link JobScheduler#cancel(int)}, {@link JobScheduler#cancelAll()}, or
     * {@link JobScheduler#cancelInAllNamespaces()} to stop an active job while losing strong
     * references to the job handle. In this case, the job is not necessarily abandoned. However,
     * the system cannot distinguish such cases from truly abandoned jobs.
     * <p>
     * It is recommended that you use {@link JobService#jobFinished(JobParameters, boolean)} or
     * return false from {@link JobService#onStartJob(JobParameters)} to stop an active job. This
     * will prevent the occurrence of this stop reason and the need to handle it. The primary use
     * case for this stop reason is to report a probable case of a job being abandoned.
     * <p>
     */
    @FlaggedApi(Flags.FLAG_HANDLE_ABANDONED_JOBS)
    public static final int STOP_REASON_TIMEOUT_ABANDONED = 16;

    /** @hide */
    @IntDef(prefix = {"STOP_REASON_"}, value = {
            STOP_REASON_UNDEFINED,
@@ -287,6 +318,7 @@ public class JobParameters implements Parcelable {
            STOP_REASON_USER,
            STOP_REASON_SYSTEM_PROCESSING,
            STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED,
            STOP_REASON_TIMEOUT_ABANDONED,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface StopReason {
@@ -361,6 +393,12 @@ public class JobParameters implements Parcelable {
    }

    /**
     * Returns the reason {@link JobService#onStopJob(JobParameters)} was called on this job.
     * <p>
     * Apps should not rely on the stop reason for critical decision-making, as additional stop
     * reasons may be added in subsequent Android releases. The primary intended use of this method
     * is for logging and diagnostic purposes to gain insights into the causes of job termination.
     * <p>
     * @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will
     * be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not
     * yet been called.
@@ -770,7 +808,7 @@ public class JobParameters implements Parcelable {
                return;
            }
            try {
                mCallback.forceJobFinished(mJobId);
                mCallback.handleAbandonedJob(mJobId);
            } catch (Exception e) {
                Log.wtf(TAG, "Could not destroy running job", e);
            }
+3 −3
Original line number Diff line number Diff line
@@ -165,11 +165,11 @@ public abstract class JobServiceEngine {
                case MSG_EXECUTE_JOB: {
                    final JobParameters params = (JobParameters) msg.obj;
                    try {
                        if (Flags.cleanupEmptyJobs()) {
                        if (Flags.handleAbandonedJobs()) {
                            params.enableCleaner();
                        }
                        boolean workOngoing = JobServiceEngine.this.onStartJob(params);
                        if (Flags.cleanupEmptyJobs() && !workOngoing) {
                        if (Flags.handleAbandonedJobs() && !workOngoing) {
                            params.disableCleaner();
                        }
                        ackStartMessage(params, workOngoing);
@@ -196,7 +196,7 @@ public abstract class JobServiceEngine {
                    IJobCallback callback = params.getCallback();
                    if (callback != null) {
                        try {
                            if (Flags.cleanupEmptyJobs()) {
                            if (Flags.handleAbandonedJobs()) {
                                params.disableCleaner();
                            }
                            callback.jobFinished(params.getJobId(), needsReschedule);
+7 −7
Original line number Diff line number Diff line
@@ -129,8 +129,8 @@ public final class JobServiceContext implements ServiceConnection {
    private static final String[] VERB_STRINGS = {
            "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED"
    };
    private static final String TRACE_JOB_FORCE_FINISHED_PREFIX = "forceJobFinished:";
    private static final String TRACE_JOB_FORCE_FINISHED_DELIMITER = "#";
    private static final String TRACE_ABANDONED_JOB = "abandonedJob:";
    private static final String TRACE_ABANDONED_JOB_DELIMITER = "#";

    // States that a job occupies while interacting with the client.
    static final int VERB_BINDING = 0;
@@ -294,8 +294,8 @@ public final class JobServiceContext implements ServiceConnection {
        }

        @Override
        public void forceJobFinished(int jobId) {
            doForceJobFinished(this, jobId);
        public void handleAbandonedJob(int jobId) {
            doHandleAbandonedJob(this, jobId);
        }

        @Override
@@ -773,7 +773,7 @@ public final class JobServiceContext implements ServiceConnection {
     * This method just adds traces to evaluate jobs that leak jobparameters at the client.
     * It does not stop the job.
     */
    void doForceJobFinished(JobCallback cb, int jobId) {
    void doHandleAbandonedJob(JobCallback cb, int jobId) {
        final long ident = Binder.clearCallingIdentity();
        try {
            final JobStatus executing;
@@ -787,9 +787,9 @@ public final class JobServiceContext implements ServiceConnection {
            }
            if (executing != null && jobId == executing.getJobId()) {
                final StringBuilder stateSuffix = new StringBuilder();
                stateSuffix.append(TRACE_JOB_FORCE_FINISHED_PREFIX);
                stateSuffix.append(TRACE_ABANDONED_JOB);
                stateSuffix.append(executing.getBatteryName());
                stateSuffix.append(TRACE_JOB_FORCE_FINISHED_DELIMITER);
                stateSuffix.append(TRACE_ABANDONED_JOB_DELIMITER);
                stateSuffix.append(executing.getJobId());
                Trace.instant(Trace.TRACE_TAG_POWER, stateSuffix.toString());
            }
Loading