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

Commit a06e85bd authored by Kweku Adams's avatar Kweku Adams Committed by Android (Google) Code Review
Browse files

Merge "Don't allow jobs of stopped apps to run." into main

parents ebc1d4f0 3d3b01ab
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -1433,10 +1433,10 @@ 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
                        // Exclude jobs scheduled on behalf of this app 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
                        // Proxied jobs will not be allowed to run if the source app is stopped.
                        cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
                                /* includeSchedulingApp */ true, /* includeSourceApp */ false,
                                JobParameters.STOP_REASON_USER,
@@ -1448,7 +1448,9 @@ public class JobSchedulerService extends com.android.server.SystemService
        }
    };

    private String getPackageName(Intent intent) {
    /** Returns the package name stored in the intent's data. */
    @Nullable
    public static String getPackageName(Intent intent) {
        Uri uri = intent.getData();
        String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
        return pkg;
+115 −4
Original line number Diff line number Diff line
@@ -17,18 +17,26 @@
package com.android.server.job.controllers;

import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.getPackageName;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
import com.android.server.AppStateTrackerImpl.Listener;
@@ -50,6 +58,8 @@ import java.util.function.Predicate;
 *
 * - the uid-active boolean state expressed by the AppStateTracker.  Jobs in 'active'
 *    uids are inherently eligible to run jobs regardless of the uid's standby bucket.
 *
 * - the app's stopped state
 */
public final class BackgroundJobsController extends StateController {
    private static final String TAG = "JobScheduler.Background";
@@ -63,9 +73,48 @@ public final class BackgroundJobsController extends StateController {

    private final ActivityManagerInternal mActivityManagerInternal;
    private final AppStateTrackerImpl mAppStateTracker;
    private final PackageManagerInternal mPackageManagerInternal;

    @GuardedBy("mLock")
    private final SparseArrayMap<String, Boolean> mPackageStoppedState = new SparseArrayMap<>();

    private final UpdateJobFunctor mUpdateJobFunctor = new UpdateJobFunctor();

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String pkgName = getPackageName(intent);
            final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
            final String action = intent.getAction();
            if (pkgUid == -1) {
                Slog.e(TAG, "Didn't get package UID in intent (" + action + ")");
                return;
            }

            if (DEBUG) {
                Slog.d(TAG, "Got " + action + " for " + pkgUid + "/" + pkgName);
            }

            switch (action) {
                case Intent.ACTION_PACKAGE_RESTARTED: {
                    synchronized (mLock) {
                        mPackageStoppedState.add(pkgUid, pkgName, Boolean.TRUE);
                        updateJobRestrictionsForUidLocked(pkgUid, false);
                    }
                }
                break;

                case Intent.ACTION_PACKAGE_UNSTOPPED: {
                    synchronized (mLock) {
                        mPackageStoppedState.add(pkgUid, pkgName, Boolean.FALSE);
                        updateJobRestrictionsLocked(pkgUid, UNKNOWN);
                    }
                }
                break;
            }
        }
    };

    public BackgroundJobsController(JobSchedulerService service) {
        super(service);

@@ -73,11 +122,18 @@ public final class BackgroundJobsController extends StateController {
                LocalServices.getService(ActivityManagerInternal.class));
        mAppStateTracker = (AppStateTrackerImpl) Objects.requireNonNull(
                LocalServices.getService(AppStateTracker.class));
        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
    }

    @Override
    public void startTrackingLocked() {
        mAppStateTracker.addListener(mForceAppStandbyListener);
        final IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
        filter.addAction(Intent.ACTION_PACKAGE_UNSTOPPED);
        filter.addDataScheme("package");
        mContext.registerReceiverAsUser(
                mBroadcastReceiver, UserHandle.ALL, filter, null, null);
    }

    @Override
@@ -98,12 +154,46 @@ public final class BackgroundJobsController extends StateController {
        }
    }

    @Override
    public void onAppRemovedLocked(String packageName, int uid) {
        mPackageStoppedState.delete(uid, packageName);
    }

    @Override
    public void onUserRemovedLocked(int userId) {
        for (int u = mPackageStoppedState.numMaps() - 1; u >= 0; --u) {
            final int uid = mPackageStoppedState.keyAt(u);
            if (UserHandle.getUserId(uid) == userId) {
                mPackageStoppedState.deleteAt(u);
            }
        }
    }

    @Override
    public void dumpControllerStateLocked(final IndentingPrintWriter pw,
            final Predicate<JobStatus> predicate) {
        pw.println("Aconfig flags:");
        pw.increaseIndent();
        pw.print(android.content.pm.Flags.FLAG_STAY_STOPPED,
                android.content.pm.Flags.stayStopped());
        pw.println();
        pw.decreaseIndent();
        pw.println();

        mAppStateTracker.dump(pw);
        pw.println();

        pw.println("Stopped packages:");
        pw.increaseIndent();
        mPackageStoppedState.forEach((uid, pkgName, isStopped) -> {
            pw.print(uid);
            pw.print(":");
            pw.print(pkgName);
            pw.print("=");
            pw.println(isStopped);
        });
        pw.println();

        mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
            final int uid = jobStatus.getSourceUid();
            final String sourcePkg = jobStatus.getSourcePackageName();
@@ -205,14 +295,34 @@ public final class BackgroundJobsController extends StateController {
        }
    }

    private boolean isPackageStopped(String packageName, int uid) {
        if (mPackageStoppedState.contains(uid, packageName)) {
            return mPackageStoppedState.get(uid, packageName);
        }
        final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
        mPackageStoppedState.add(uid, packageName, isStopped);
        return isStopped;
    }

    boolean updateSingleJobRestrictionLocked(JobStatus jobStatus, final long nowElapsed,
            int activeState) {
        final int uid = jobStatus.getSourceUid();
        final String packageName = jobStatus.getSourcePackageName();

        final boolean isUserBgRestricted =
                !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
                        && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName);
        final boolean isSourcePkgStopped =
                isPackageStopped(jobStatus.getSourcePackageName(), jobStatus.getSourceUid());
        final boolean isCallingPkgStopped;
        if (!jobStatus.isProxyJob()) {
            isCallingPkgStopped = isSourcePkgStopped;
        } else {
            isCallingPkgStopped =
                    isPackageStopped(jobStatus.getCallingPackageName(), jobStatus.getUid());
        }
        final boolean isStopped = android.content.pm.Flags.stayStopped()
                && (isCallingPkgStopped || isSourcePkgStopped);
        final boolean isUserBgRestricted = isStopped
                || (!mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
                        && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
        // If a job started with the foreground flag, it'll cause the UID to stay active
        // and thus cause areJobsRestricted() to always return false, so if
        // areJobsRestricted() returns false and the app is BG restricted and not TOP,
@@ -233,7 +343,8 @@ public final class BackgroundJobsController extends StateController {
                && isUserBgRestricted
                && mService.getUidProcState(uid)
                        > ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
        final boolean canRun = !shouldStopImmediately
        // Don't let jobs (including proxied jobs) run if the app is in the stopped state.
        final boolean canRun = !isStopped && !shouldStopImmediately
                && !mAppStateTracker.areJobsRestricted(
                        uid, packageName, jobStatus.canRunInBatterySaver());

+6 −0
Original line number Diff line number Diff line
@@ -1102,6 +1102,12 @@ public final class JobStatus {
        return job.getService();
    }

    /** Return the package name of the app that scheduled the job. */
    public String getCallingPackageName() {
        return job.getService().getPackageName();
    }

    /** Return the package name of the app on whose behalf the job was scheduled. */
    public String getSourcePackageName() {
        return sourcePackageName;
    }
+1 −1
Original line number Diff line number Diff line
@@ -2805,7 +2805,7 @@ public class Intent implements Parcelable, Cloneable {
     * and the package in the stopped state cannot self-start for any reason unless there's an
     * explicit request to start a component in the package. The {@link #ACTION_PACKAGE_UNSTOPPED}
     * broadcast is sent when such an explicit process start occurs and the package is taken
     * out of the stopped state.
     * out of the stopped state. The data contains the name of the package.
     * </p>
     * <ul>
     * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+0 −16
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.server.content;
import android.annotation.Nullable;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.pm.PackageManagerInternal;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
@@ -29,7 +28,6 @@ import android.util.SparseBooleanArray;
import android.util.SparseLongArray;

import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;

public class SyncJobService extends JobService {
    private static final String TAG = "SyncManager";
@@ -99,20 +97,6 @@ public class SyncJobService extends JobService {
            return true;
        }

        // TODO(b/209852664): remove this logic from here once it's added within JobScheduler.
        // JobScheduler should not call onStartJob for syncs whose source packages are stopped.
        // Until JS adds the relevant logic, this is a temporary solution to keep deferring syncs
        // for packages in the stopped state.
        if (android.content.pm.Flags.stayStopped()) {
            if (LocalServices.getService(PackageManagerInternal.class)
                    .isPackageStopped(op.owningPackage, op.target.userId)) {
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Slog.d(TAG, "Skipping sync for force-stopped package: " + op.owningPackage);
                }
                return false;
            }
        }

        boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
        synchronized (sLock) {
            final int jobId = params.getJobId();
Loading