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

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

Ensure UidStats is not null when needed.

We're sometimes being told that an app has been uninstalled before the
process has stopped. That puts JS and especially ConnectivityController
in an inconsistent state. Make sure we don't crash because of the
inconsistency.

Bug: 183921387
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: Ied17a461a47160c81042676f43d7f8defe1c4466
parent 81b12816
Loading
Loading
Loading
Loading
+34 −25
Original line number Diff line number Diff line
@@ -188,11 +188,8 @@ public final class ConnectivityController extends RestrictingController implemen
    @Override
    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
        if (jobStatus.hasConnectivityConstraint()) {
            UidStats uidStats = mUidStats.get(jobStatus.getSourceUid());
            if (uidStats == null) {
                uidStats = new UidStats(jobStatus.getSourceUid());
                mUidStats.append(jobStatus.getSourceUid(), uidStats);
            }
            final UidStats uidStats =
                    getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), false);
            if (wouldBeReadyWithConstraintLocked(jobStatus, JobStatus.CONSTRAINT_CONNECTIVITY)) {
                uidStats.numReadyWithConnectivity++;
            }
@@ -211,7 +208,8 @@ public final class ConnectivityController extends RestrictingController implemen
    @Override
    public void prepareForExecutionLocked(JobStatus jobStatus) {
        if (jobStatus.hasConnectivityConstraint()) {
            UidStats uidStats = mUidStats.get(jobStatus.getSourceUid());
            final UidStats uidStats =
                    getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
            uidStats.numRunning++;
        }
    }
@@ -225,23 +223,11 @@ public final class ConnectivityController extends RestrictingController implemen
            if (jobs != null) {
                jobs.remove(jobStatus);
            }
            UidStats us = mUidStats.get(jobStatus.getSourceUid());
            if (us == null) {
                // This shouldn't be happening. We create a UidStats object for the app when the
                // first job is scheduled in maybeStartTrackingJobLocked() and only ever drop the
                // object if the app is uninstalled or the user is removed. That means that if we
                // end up in this situation, onAppRemovedLocked() or onUserRemovedLocked() was
                // called before maybeStopTrackingJobLocked(), which is the reverse order of what
                // JobSchedulerService does (JSS calls maybeStopTrackingJobLocked() for all jobs
                // before calling onAppRemovedLocked() or onUserRemovedLocked()).
                Slog.wtfStack(TAG,
                        "UidStats was null after job for " + jobStatus.getSourcePackageName()
                                + " was registered");
            } else {
                us.numReadyWithConnectivity--;
            final UidStats uidStats =
                    getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
            uidStats.numReadyWithConnectivity--;
            if (jobStatus.madeActive != 0) {
                    us.numRunning--;
                }
                uidStats.numRunning--;
            }
            maybeRevokeStandbyExceptionLocked(jobStatus);
            maybeAdjustRegisteredCallbacksLocked();
@@ -266,6 +252,27 @@ public final class ConnectivityController extends RestrictingController implemen
        }
    }

    @NonNull
    private UidStats getUidStats(int uid, String packageName, boolean shouldExist) {
        UidStats us = mUidStats.get(uid);
        if (us == null) {
            if (shouldExist) {
                // This shouldn't be happening. We create a UidStats object for the app when the
                // first job is scheduled in maybeStartTrackingJobLocked() and only ever drop the
                // object if the app is uninstalled or the user is removed. That means that if we
                // end up in this situation, onAppRemovedLocked() or onUserRemovedLocked() was
                // called before maybeStopTrackingJobLocked(), which is the reverse order of what
                // JobSchedulerService does (JSS calls maybeStopTrackingJobLocked() for all jobs
                // before calling onAppRemovedLocked() or onUserRemovedLocked()).
                Slog.wtfStack(TAG,
                        "UidStats was null after job for " + packageName + " was registered");
            }
            us = new UidStats(uid);
            mUidStats.append(uid, us);
        }
        return us;
    }

    /**
     * Returns true if the job's requested network is available. This DOES NOT necessarily mean
     * that the UID has been granted access to the network.
@@ -332,7 +339,8 @@ public final class ConnectivityController extends RestrictingController implemen
            return;
        }

        UidStats uidStats = mUidStats.get(jobStatus.getSourceUid());
        final UidStats uidStats =
                getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);

        if (jobStatus.shouldTreatAsExpeditedJob()) {
            if (!jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)) {
@@ -584,7 +592,8 @@ public final class ConnectivityController extends RestrictingController implemen
        if (mCurrentDefaultNetworkCallbacks.contains(sourceUid)) {
            return;
        }
        UidStats uidStats = mUidStats.get(sourceUid);
        final UidStats uidStats =
                getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
        if (!mSortedStats.contains(uidStats)) {
            mSortedStats.add(uidStats);
        }