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

Commit 13c5ea1f authored by Kweku Adams's avatar Kweku Adams
Browse files

Cancel all jobs associated with an uninstalled app.

Make sure we don't leave around any jobs scheduled on behalf of the
uninstalled app when it is uninstalled.

Bug: 247808104
Test: atest CtsJobSchedulerTestCases
Change-Id: I9114b7670bc20bffe9666c48bbf244d87f097d3b
parent 5cb56048
Loading
Loading
Loading
Loading
+37 −11
Original line number Diff line number Diff line
@@ -882,6 +882,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                                            // a user-initiated action, it should be fine to just
                                            // put USER instead of UNINSTALL or DISABLED.
                                            cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
                                                    /* includeSchedulingApp */ true,
                                                    /* includeSourceApp */ true,
                                                    JobParameters.STOP_REASON_USER,
                                                    JobParameters.INTERNAL_STOP_REASON_UNINSTALL,
                                                    "app disabled");
@@ -932,6 +934,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                    // 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, pkgUid,
                            /* includeSchedulingApp */ true, /* includeSourceApp */ true,
                            JobParameters.STOP_REASON_USER,
                            JobParameters.INTERNAL_STOP_REASON_UNINSTALL, "app uninstalled");
                    for (int c = 0; c < mControllers.size(); ++c) {
@@ -986,7 +989,12 @@ public class JobSchedulerService extends com.android.server.SystemService
                        Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
                    }
                    synchronized (mLock) {
                        // Exclude jobs scheduled on behalf of this app for now because SyncManager
                        // and other job proxy agents may not know to reschedule the job properly
                        // after force stop.
                        // TODO(209852664): determine how to best handle syncs & other proxied jobs
                        cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
                                /* includeSchedulingApp */ true, /* includeSourceApp */ false,
                                JobParameters.STOP_REASON_USER,
                                JobParameters.INTERNAL_STOP_REASON_CANCELED,
                                "app force stopped");
@@ -1304,16 +1312,18 @@ public class JobSchedulerService extends com.android.server.SystemService
        }
    }

    private void cancelJobsForUserLocked(int userHandle) {
        final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
        for (int i = 0; i < jobsForUser.size(); i++) {
            JobStatus toRemove = jobsForUser.get(i);
            // 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.
    private final Consumer<JobStatus> mCancelJobDueToUserRemovalConsumer = (toRemove) -> {
        // 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,
                JobParameters.INTERNAL_STOP_REASON_UNINSTALL, "user removed");
        }
    };

    private void cancelJobsForUserLocked(int userHandle) {
        mJobs.forEachJob(
                (job) -> job.getUserId() == userHandle || job.getSourceUserId() == userHandle,
                mCancelJobDueToUserRemovalConsumer);
    }

    private void cancelJobsForNonExistentUsers() {
@@ -1324,15 +1334,31 @@ public class JobSchedulerService extends com.android.server.SystemService
    }

    private void cancelJobsForPackageAndUidLocked(String pkgName, int uid,
            boolean includeSchedulingApp, boolean includeSourceApp,
            @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
        if (!includeSchedulingApp && !includeSourceApp) {
            Slog.wtfStack(TAG,
                    "Didn't indicate whether to cancel jobs for scheduling and/or source app");
            includeSourceApp = true;
        }
        if ("android".equals(pkgName)) {
            Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
            return;
        }
        final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
        final List<JobStatus> jobsForUid = new ArrayList<>();
        if (includeSchedulingApp) {
            mJobs.getJobsByUid(uid, jobsForUid);
        }
        if (includeSourceApp) {
            mJobs.getJobsBySourceUid(uid, jobsForUid);
        }
        for (int i = jobsForUid.size() - 1; i >= 0; i--) {
            final JobStatus job = jobsForUid.get(i);
            if (job.getSourcePackageName().equals(pkgName)) {
            final boolean shouldCancel =
                    (includeSchedulingApp
                            && job.getServiceComponent().getPackageName().equals(pkgName))
                    || (includeSourceApp && job.getSourcePackageName().equals(pkgName));
            if (shouldCancel) {
                cancelJobImplLocked(job, null, reason, internalReasonCode, debugReason);
            }
        }
+33 −18
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sSystemClock;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.content.ComponentName;
@@ -32,7 +33,6 @@ import android.os.Handler;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArraySet;
@@ -287,26 +287,37 @@ public final class JobStore {
    }

    /**
     * @param userHandle User for whom we are querying the list of jobs.
     * @return A list of all the jobs scheduled for the provided user. Never null.
     * @param sourceUid Uid of the source app.
     * @return A list of all the jobs scheduled for the source app. Never null.
     */
    public List<JobStatus> getJobsByUser(int userHandle) {
        return mJobSet.getJobsByUser(userHandle);
    @NonNull
    public List<JobStatus> getJobsBySourceUid(int sourceUid) {
        return mJobSet.getJobsBySourceUid(sourceUid);
    }

    public void getJobsBySourceUid(int sourceUid, @NonNull List<JobStatus> insertInto) {
        mJobSet.getJobsBySourceUid(sourceUid, insertInto);
    }

    /**
     * @param uid Uid of the requesting app.
     * @return All JobStatus objects for a given uid from the master list. Never null.
     */
    @NonNull
    public List<JobStatus> getJobsByUid(int uid) {
        return mJobSet.getJobsByUid(uid);
    }

    public void getJobsByUid(int uid, @NonNull List<JobStatus> insertInto) {
        mJobSet.getJobsByUid(uid, insertInto);
    }

    /**
     * @param uid Uid of the requesting app.
     * @param jobId Job id, specified at schedule-time.
     * @return the JobStatus that matches the provided uId and jobId, or null if none found.
     */
    @Nullable
    public JobStatus getJobByUidAndJobId(int uid, int jobId) {
        return mJobSet.get(uid, jobId);
    }
@@ -1207,25 +1218,29 @@ public final class JobStore {

        public List<JobStatus> getJobsByUid(int uid) {
            ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
            getJobsByUid(uid, matchingJobs);
            return matchingJobs;
        }

        public void getJobsByUid(int uid, List<JobStatus> insertInto) {
            ArraySet<JobStatus> jobs = mJobs.get(uid);
            if (jobs != null) {
                matchingJobs.addAll(jobs);
                insertInto.addAll(jobs);
            }
            return matchingJobs;
        }

        // By user, not by uid, so we need to traverse by key and check
        public List<JobStatus> getJobsByUser(int userId) {
        @NonNull
        public List<JobStatus> getJobsBySourceUid(int sourceUid) {
            final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
            for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
                if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
                    final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
                    if (jobs != null) {
                        result.addAll(jobs);
                    }
            getJobsBySourceUid(sourceUid, result);
            return result;
        }

        public void getJobsBySourceUid(int sourceUid, List<JobStatus> insertInto) {
            final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
            if (jobs != null) {
                insertInto.addAll(jobs);
            }
            return result;
        }

        public boolean add(JobStatus job) {
@@ -1369,7 +1384,7 @@ public final class JobStore {
        }

        public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate,
                Consumer<JobStatus> functor) {
                @NonNull Consumer<JobStatus> functor) {
            for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
                ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
                if (jobs != null) {