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

Commit 36bd8292 authored by Kweku Adams's avatar Kweku Adams
Browse files

Better handle uninstall cases.

1. Processes may not have been fully terminated by the time
   PackageManager broadcasts PACKAGE_FULLY_REMOVED (see b/165012101
   and b/162757029), so note that in the comments.
2. Use ACTION_PACKAGE_FULLY_REMOVED instead of ACTION_PACKAGE_REMOVED to
   avoid processing update broadcasts.
3. Make sure we don't attempt to reschedule or reprocess jobs of
   uninstalled apps.

Bug: 184098842
Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job
Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job
Test: atest CtsJobSchedulerTestCases
Change-Id: I355786885fcc8eccd31c5557b35de3e5243615d8
parent 9496d117
Loading
Loading
Loading
Loading
+12 −2
Original line number Diff line number Diff line
@@ -64,6 +64,16 @@ public class JobParameters implements Parcelable {
     */
    public static final int REASON_RESTRICTED_BUCKET =
            JobProtoEnums.STOP_REASON_RESTRICTED_BUCKET; // 6.
    /**
     * The app was uninstalled.
     * @hide
     */
    public static  final int DEBUG_REASON_UNINSTALL = 7;
    /**
     * The app's data was cleared.
     * @hide
     */
    public static  final int DEBUG_REASON_DATA_CLEARED = 8;

    /**
     * All the stop reason codes. This should be regarded as an immutable array at runtime.
@@ -187,8 +197,8 @@ public class JobParameters implements Parcelable {
     */
    public static final int STOP_REASON_APP_STANDBY = 12;
    /**
     * The user stopped the job. This can happen either through force-stop, or via adb shell
     * commands.
     * The user stopped the job. This can happen either through force-stop, adb shell commands,
     * or uninstalling.
     */
    public static final int STOP_REASON_USER = 13;
    /** The system is doing some processing that requires stopping this job. */
+2 −1
Original line number Diff line number Diff line
@@ -37,7 +37,8 @@ public interface JobSchedulerInternal {
    /**
     * Cancel the jobs for a given uid (e.g. when app data is cleared)
     */
    void cancelJobsForUid(int uid, @JobParameters.StopReason int reason, String debugReason);
    void cancelJobsForUid(int uid, @JobParameters.StopReason int reason, int debugReasonCode,
            String debugReason);

    /**
     * These are for activity manager to communicate to use what is currently performing backups.
+62 −48
Original line number Diff line number Diff line
@@ -747,12 +747,14 @@ public class JobSchedulerService extends com.android.server.SystemService
                                                    + " in user " + userId);
                                        }
                                        synchronized (mLock) {
                                            // By the time we get here, the process should have
                                            // already been stopped, so the app wouldn't get the
                                            // stop reason, so just put USER instead of UNINSTALL
                                            // or DISABLED.
                                            // There's no guarantee that the process has been
                                            // stopped by the time we get here, but since this is
                                            // a user-initiated action, it should be fine to just
                                            // put USER instead of UNINSTALL or DISABLED.
                                            cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
                                                    JobParameters.STOP_REASON_USER, "app disabled");
                                                    JobParameters.STOP_REASON_USER,
                                                    JobParameters.DEBUG_REASON_UNINSTALL,
                                                    "app disabled");
                                        }
                                    }
                                } catch (RemoteException|IllegalArgumentException e) {
@@ -783,26 +785,23 @@ public class JobSchedulerService extends com.android.server.SystemService
                } else {
                    Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
                }
            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
                // If this is an outright uninstall rather than the first half of an
                // app update sequence, cancel the jobs associated with the app.
                if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
            } else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
                int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
                if (DEBUG) {
                    Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
                }
                    // By the time we get here, the process should have already
                    // been stopped, so the app wouldn't get the stop reason,
                    // so just put USER instead of UNINSTALL or DISABLED.
                synchronized (mLock) {
                    // There's no guarantee that the process has been stopped by the time we
                    // get here, but since this is generally a user-initiated action, it should
                    // be fine to just put USER instead of UNINSTALL or DISABLED.
                    cancelJobsForPackageAndUidLocked(pkgName, uidRemoved,
                                JobParameters.STOP_REASON_USER, "app uninstalled");
                            JobParameters.STOP_REASON_USER,
                            JobParameters.DEBUG_REASON_UNINSTALL, "app uninstalled");
                    for (int c = 0; c < mControllers.size(); ++c) {
                        mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
                    }
                    mDebuggableApps.remove(pkgName);
                }
                }
            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                synchronized (mLock) {
@@ -849,7 +848,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                    }
                    synchronized (mLock) {
                        cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
                                JobParameters.STOP_REASON_USER, "app force stopped");
                                JobParameters.STOP_REASON_USER, JobParameters.REASON_CANCELED,
                                "app force stopped");
                    }
                }
            }
@@ -1038,7 +1038,7 @@ public class JobSchedulerService extends com.android.server.SystemService
            if (toCancel != null) {
                // Implicitly replaces the existing job record with the new instance
                cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP,
                        "job rescheduled by app");
                        JobParameters.REASON_CANCELED, "job rescheduled by app");
            } else {
                startTrackingJobLocked(jobStatus, null);
            }
@@ -1115,10 +1115,11 @@ public class JobSchedulerService extends com.android.server.SystemService
        final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
        for (int i = 0; i < jobsForUser.size(); i++) {
            JobStatus toRemove = jobsForUser.get(i);
            // By the time we get here, the process should have already been stopped, so the
            // app wouldn't get the stop reason, so just put USER instead of UNINSTALL.
            // There's no guarantee that the process has been stopped by the time we get here,
            // but since this is a user-initiated action, it should be fine to just put USER
            // instead of UNINSTALL or DISABLED.
            cancelJobImplLocked(toRemove, null, JobParameters.STOP_REASON_USER,
                    "user removed");
                    JobParameters.DEBUG_REASON_UNINSTALL, "user removed");
        }
    }

@@ -1130,7 +1131,7 @@ public class JobSchedulerService extends com.android.server.SystemService
    }

    private void cancelJobsForPackageAndUidLocked(String pkgName, int uid,
            @JobParameters.StopReason int reason, String debugReason) {
            @JobParameters.StopReason int reason, int debugReasonCode, String debugReason) {
        if ("android".equals(pkgName)) {
            Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
            return;
@@ -1139,7 +1140,7 @@ public class JobSchedulerService extends com.android.server.SystemService
        for (int i = jobsForUid.size() - 1; i >= 0; i--) {
            final JobStatus job = jobsForUid.get(i);
            if (job.getSourcePackageName().equals(pkgName)) {
                cancelJobImplLocked(job, null, reason, debugReason);
                cancelJobImplLocked(job, null, reason, debugReasonCode, debugReason);
            }
        }
    }
@@ -1152,7 +1153,7 @@ public class JobSchedulerService extends com.android.server.SystemService
     * @param uid Uid to check against for removal of a job.
     */
    public boolean cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
            String debugReason) {
            int debugReasonCode, String debugReason) {
        if (uid == Process.SYSTEM_UID) {
            Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
            return false;
@@ -1163,7 +1164,7 @@ public class JobSchedulerService extends com.android.server.SystemService
            final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
            for (int i = 0; i < jobsForUid.size(); i++) {
                JobStatus toRemove = jobsForUid.get(i);
                cancelJobImplLocked(toRemove, null, reason, debugReason);
                cancelJobImplLocked(toRemove, null, reason, debugReasonCode, debugReason);
                jobsCanceled = true;
            }
        }
@@ -1185,6 +1186,7 @@ public class JobSchedulerService extends com.android.server.SystemService
            toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
            if (toCancel != null) {
                cancelJobImplLocked(toCancel, null, reason,
                        JobParameters.REASON_CANCELED,
                        "cancel() called by app, callingUid=" + callingUid
                                + " uid=" + uid + " jobId=" + jobId);
            }
@@ -1199,7 +1201,7 @@ public class JobSchedulerService extends com.android.server.SystemService
     * currently scheduled jobs.
     */
    private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
            @JobParameters.StopReason int reason, String debugReason) {
            @JobParameters.StopReason int reason, int debugReasonCode, String debugReason) {
        if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
        cancelled.unprepareLocked();
        stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
@@ -1208,8 +1210,7 @@ public class JobSchedulerService extends com.android.server.SystemService
            mJobPackageTracker.noteNonpending(cancelled);
        }
        // Cancel if running.
        stopJobOnServiceContextLocked(cancelled, reason, JobParameters.REASON_CANCELED,
                debugReason);
        stopJobOnServiceContextLocked(cancelled, reason, debugReasonCode, debugReason);
        // If this is a replacement, bring in the new version of the job
        if (incomingJob != null) {
            if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());
@@ -1459,7 +1460,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                    Slog.v(TAG, "  replacing " + oldJob + " with " + newJob);
                }
                cancelJobImplLocked(oldJob, newJob, JobParameters.STOP_REASON_SYSTEM_PROCESSING,
                        "deferred rtc calculation");
                        JobParameters.REASON_CANCELED, "deferred rtc calculation");
            }
        }
    };
@@ -1482,7 +1483,7 @@ public class JobSchedulerService extends com.android.server.SystemService

            // Register br for package removals and user removals.
            final IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
            filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
@@ -1786,15 +1787,26 @@ public class JobSchedulerService extends com.android.server.SystemService
     * @param needsReschedule Whether the implementing class should reschedule this job.
     */
    @Override
    public void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule) {
    public void onJobCompletedLocked(JobStatus jobStatus, int debugStopReason,
            boolean needsReschedule) {
        if (DEBUG) {
            Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
            Slog.d(TAG, "Completed " + jobStatus + ", reason=" + debugStopReason
                    + ", reschedule=" + needsReschedule);
        }

        mLastCompletedJobs[mLastCompletedJobIndex] = jobStatus;
        mLastCompletedJobTimeElapsed[mLastCompletedJobIndex] = sElapsedRealtimeClock.millis();
        mLastCompletedJobIndex = (mLastCompletedJobIndex + 1) % NUM_COMPLETED_JOB_HISTORY;

        if (debugStopReason == JobParameters.DEBUG_REASON_UNINSTALL
                || debugStopReason == JobParameters.DEBUG_REASON_DATA_CLEARED) {
            // The job should have already been cleared from the rest of the JS tracking. No need
            // to go through all that flow again.
            jobStatus.unprepareLocked();
            reportActiveLocked();
            return;
        }

        // Intentionally not checking expedited job quota here. An app can't find out if it's run
        // out of quota when it asks JS to reschedule an expedited job. Instead, the rescheduled
        // EJ will just be demoted to a regular job if the app has no EJ quota left.
@@ -1805,8 +1817,8 @@ public class JobSchedulerService extends com.android.server.SystemService
        final JobStatus rescheduledJob = needsReschedule
                ? getRescheduleJobForFailureLocked(jobStatus) : null;
        if (rescheduledJob != null
                && (stopReason == JobParameters.REASON_TIMEOUT
                || stopReason == JobParameters.REASON_PREEMPT)) {
                && (debugStopReason == JobParameters.REASON_TIMEOUT
                || debugStopReason == JobParameters.REASON_PREEMPT)) {
            rescheduledJob.disallowRunInBatterySaverAndDoze();
        }

@@ -1911,6 +1923,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                        break;
                    case MSG_STOP_JOB:
                        cancelJobImplLocked((JobStatus) message.obj, null, message.arg1,
                                JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
                                "app no longer allowed to run");
                        break;

@@ -1927,7 +1940,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                        if (disabled) {
                            cancelJobsForUid(uid,
                                    JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
                                    "uid gone");
                                    JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED, "uid gone");
                        }
                        synchronized (mLock) {
                            mDeviceIdleJobsController.setUidActiveLocked(uid, false);
@@ -1947,7 +1960,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                        if (disabled) {
                            cancelJobsForUid(uid,
                                    JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
                                    "app uid idle");
                                    JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED, "app uid idle");
                        }
                        synchronized (mLock) {
                            mDeviceIdleJobsController.setUidActiveLocked(uid, false);
@@ -2409,8 +2422,8 @@ public class JobSchedulerService extends com.android.server.SystemService

        @Override
        public void cancelJobsForUid(int uid, @JobParameters.StopReason int reason,
                String debugReason) {
            JobSchedulerService.this.cancelJobsForUid(uid, reason, debugReason);
                int debugReasonCode, String debugReason) {
            JobSchedulerService.this.cancelJobsForUid(uid, reason, debugReasonCode, debugReason);
        }

        @Override
@@ -2749,6 +2762,7 @@ public class JobSchedulerService extends com.android.server.SystemService
            try {
                JobSchedulerService.this.cancelJobsForUid(uid,
                        JobParameters.STOP_REASON_CANCELLED_BY_APP,
                        JobParameters.REASON_CANCELED,
                        "cancelAll() called by app, callingUid=" + uid);
            } finally {
                Binder.restoreCallingIdentity(ident);
@@ -2969,7 +2983,7 @@ public class JobSchedulerService extends com.android.server.SystemService
        if (!hasJobId) {
            pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
            if (!cancelJobsForUid(pkgUid, JobParameters.STOP_REASON_USER,
                    "cancel shell command for package")) {
                    JobParameters.REASON_CANCELED, "cancel shell command for package")) {
                pw.println("No matching jobs found.");
            }
        } else {
+4 −1
Original line number Diff line number Diff line
@@ -244,7 +244,10 @@ public final class ConnectivityController extends RestrictingController implemen
                    getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
            uidStats.numReadyWithConnectivity--;
            if (jobStatus.madeActive != 0) {
                uidStats.numRunning--;
                // numRunning would be 0 if the UidStats object didn't exist before this method
                // was called. getUidStats() handles logging, so just make sure we don't save a
                // negative value.
                uidStats.numRunning = Math.max(0, uidStats.numRunning - 1);
            }
            maybeRevokeStandbyExceptionLocked(jobStatus);
            postAdjustCallbacks();
+1 −0
Original line number Diff line number Diff line
@@ -735,6 +735,7 @@ public final class QuotaController extends StateController {
        mTempAllowlistCache.delete(uid);
        mTempAllowlistGraceCache.delete(uid);
        mTopAppCache.delete(uid);
        mTopAppTrackers.delete(uid);
        mTopAppGraceCache.delete(uid);
    }

Loading