Merge "Revert "Require network permission for networked jobs."" into udc-dev
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 37ceb09..805dfaf 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1382,12 +1382,6 @@
          * Calling this method will override any requirements previously defined
          * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
          * want to call one of these methods.
-         *
-         * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
-         * an app must hold the {@link android.Manifest.permission#INTERNET} and
-         * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to
-         * schedule a job that requires a network.
-         *
          * <p class="note">
          * When your job executes in
          * {@link JobService#onStartJob(JobParameters)}, be sure to use the
@@ -1444,11 +1438,6 @@
          * otherwise you'll use the default network which may not meet this
          * constraint.
          *
-         * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
-         * an app must hold the {@link android.Manifest.permission#INTERNET} and
-         * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to
-         * schedule a job that requires a network.
-         *
          * @param networkRequest The detailed description of the kind of network
          *            this job requires, or {@code null} if no specific kind of
          *            network is required. Defining a {@link NetworkSpecifier}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index bf4f9a8..32502ed 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -481,10 +481,6 @@
      * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over
      * a metered network when there is a surplus of metered data available.
      *
-     * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
-     * this will return {@code null} if the app does not hold the permissions specified in
-     * {@link JobInfo.Builder#setRequiredNetwork(NetworkRequest)}.
-     *
      * @return the network that should be used to perform any network requests
      *         for this job, or {@code null} if this job didn't set any required
      *         network type or if the job executed when there was no available network to use.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index d06596f..d94993d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -23,7 +23,6 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
-import android.Manifest;
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -191,14 +190,6 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L;
 
-    /**
-     * Require the app to have the INTERNET and ACCESS_NETWORK_STATE permissions when scheduling
-     * a job with a connectivity constraint.
-     */
-    @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
-    static final long REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS = 271850009L;
-
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public static Clock sSystemClock = Clock.systemUTC();
 
@@ -308,14 +299,6 @@
     private final RemoteCallbackList<IUserVisibleJobObserver> mUserVisibleJobObservers =
             new RemoteCallbackList<>();
 
-    /**
-     * Cache of grant status of permissions, keyed by UID->PID->permission name. A missing value
-     * means the state has not been queried.
-     */
-    @GuardedBy("mPermissionCache")
-    private final SparseArray<SparseArrayMap<String, Boolean>> mPermissionCache =
-            new SparseArray<>();
-
     private final CountQuotaTracker mQuotaTracker;
     private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
     private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
@@ -1059,10 +1042,6 @@
             final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
 
             if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
-                synchronized (mPermissionCache) {
-                    // Something changed. Better clear the cached permission set.
-                    mPermissionCache.remove(pkgUid);
-                }
                 // Purge the app's jobs if the whole package was just disabled.  When this is
                 // the case the component name will be a bare package name.
                 if (pkgName != null && pkgUid != -1) {
@@ -1127,19 +1106,17 @@
                     Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
                 }
             } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
-                synchronized (mPermissionCache) {
-                    // Something changed. Better clear the cached permission set.
-                    mPermissionCache.remove(pkgUid);
-                }
                 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                    final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
                     synchronized (mLock) {
-                        mUidToPackageCache.remove(pkgUid);
+                        mUidToPackageCache.remove(uid);
+                    }
+                } else {
+                    synchronized (mJobSchedulerStub.mPersistCache) {
+                        mJobSchedulerStub.mPersistCache.remove(pkgUid);
                     }
                 }
             } else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
-                synchronized (mPermissionCache) {
-                    mPermissionCache.remove(pkgUid);
-                }
                 if (DEBUG) {
                     Slog.d(TAG, "Removing jobs for " + pkgName + " (uid=" + pkgUid + ")");
                 }
@@ -1178,14 +1155,6 @@
                     }
                 }
                 mConcurrencyManager.onUserRemoved(userId);
-                synchronized (mPermissionCache) {
-                    for (int u = mPermissionCache.size() - 1; u >= 0; --u) {
-                        final int uid = mPermissionCache.keyAt(u);
-                        if (userId == UserHandle.getUserId(uid)) {
-                            mPermissionCache.removeAt(u);
-                        }
-                    }
-                }
             } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
                 // Has this package scheduled any jobs, such that we will take action
                 // if it were to be force-stopped?
@@ -3779,38 +3748,18 @@
     }
 
     /**
-     * Returns whether the app has the permission granted.
-     * This currently only works for normal permissions and <b>DOES NOT</b> work for runtime
-     * permissions.
-     * TODO: handle runtime permissions
-     */
-    private boolean hasPermission(int uid, int pid, @NonNull String permission) {
-        synchronized (mPermissionCache) {
-            SparseArrayMap<String, Boolean> pidPermissions = mPermissionCache.get(uid);
-            if (pidPermissions == null) {
-                pidPermissions = new SparseArrayMap<>();
-                mPermissionCache.put(uid, pidPermissions);
-            }
-            final Boolean cached = pidPermissions.get(pid, permission);
-            if (cached != null) {
-                return cached;
-            }
-
-            final int result = getContext().checkPermission(permission, pid, uid);
-            final boolean permissionGranted = (result == PackageManager.PERMISSION_GRANTED);
-            pidPermissions.add(pid, permission, permissionGranted);
-            return permissionGranted;
-        }
-    }
-
-    /**
      * Binder stub trampoline implementation
      */
     final class JobSchedulerStub extends IJobScheduler.Stub {
+        /**
+         * Cache determination of whether a given app can persist jobs
+         * key is uid of the calling app; value is undetermined/true/false
+         */
+        private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
+
         // Enforce that only the app itself (or shared uid participant) can schedule a
         // job that runs one of the app's services, as well as verifying that the
         // named service properly requires the BIND_JOB_SERVICE permission
-        // TODO(141645789): merge enforceValidJobRequest() with validateJob()
         private void enforceValidJobRequest(int uid, int pid, JobInfo job) {
             final PackageManager pm = getContext()
                     .createContextAsUser(UserHandle.getUserHandleForUid(uid), 0)
@@ -3835,33 +3784,31 @@
                 throw new IllegalArgumentException(
                         "Tried to schedule job for non-existent component: " + service);
             }
-            // If we get this far we're good to go; all we need to do now is check
-            // whether the app is allowed to persist its scheduled work.
             if (job.isPersisted() && !canPersistJobs(pid, uid)) {
                 throw new IllegalArgumentException("Requested job cannot be persisted without"
                         + " holding android.permission.RECEIVE_BOOT_COMPLETED permission");
             }
-            if (job.getRequiredNetwork() != null
-                    && CompatChanges.isChangeEnabled(
-                            REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) {
-                // All networking, including with the local network and even local to the device,
-                // requires the INTERNET permission.
-                if (!hasPermission(uid, pid, Manifest.permission.INTERNET)) {
-                    throw new SecurityException(Manifest.permission.INTERNET
-                            + " required for jobs with a connectivity constraint");
-                }
-                if (!hasPermission(uid, pid, Manifest.permission.ACCESS_NETWORK_STATE)) {
-                    throw new SecurityException(Manifest.permission.ACCESS_NETWORK_STATE
-                            + " required for jobs with a connectivity constraint");
-                }
-            }
         }
 
         private boolean canPersistJobs(int pid, int uid) {
-            // Persisting jobs is tantamount to running at boot, so we permit
-            // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
-            // permission
-            return hasPermission(uid, pid, Manifest.permission.RECEIVE_BOOT_COMPLETED);
+            // If we get this far we're good to go; all we need to do now is check
+            // whether the app is allowed to persist its scheduled work.
+            final boolean canPersist;
+            synchronized (mPersistCache) {
+                Boolean cached = mPersistCache.get(uid);
+                if (cached != null) {
+                    canPersist = cached.booleanValue();
+                } else {
+                    // Persisting jobs is tantamount to running at boot, so we permit
+                    // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
+                    // permission
+                    int result = getContext().checkPermission(
+                            android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
+                    canPersist = (result == PackageManager.PERMISSION_GRANTED);
+                    mPersistCache.put(uid, canPersist);
+                }
+            }
+            return canPersist;
         }
 
         private int validateJob(@NonNull JobInfo job, int callingUid, int callingPid,
@@ -4080,8 +4027,6 @@
                         + " not permitted to schedule jobs for other apps");
             }
 
-            enforceValidJobRequest(callerUid, callerPid, job);
-
             int result = validateJob(job, callerUid, callerPid, userId, packageName, null);
             if (result != JobScheduler.RESULT_SUCCESS) {
                 return result;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 1e2ef77..b080bf3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -21,7 +21,6 @@
 import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
-import android.Manifest;
 import android.annotation.BytesLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -40,7 +39,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.net.Network;
 import android.net.Uri;
@@ -341,13 +339,12 @@
                 job.changedAuthorities.toArray(triggeredAuthorities);
             }
             final JobInfo ji = job.getJob();
-            final Network passedNetwork = canGetNetworkInformation(job) ? job.network : null;
             mParams = new JobParameters(mRunningCallback, job.getNamespace(), job.getJobId(),
                     ji.getExtras(),
                     ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
                     isDeadlineExpired, job.shouldTreatAsExpeditedJob(),
                     job.shouldTreatAsUserInitiatedJob(), triggeredUris, triggeredAuthorities,
-                    passedNetwork);
+                    job.network);
             mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
             mMinExecutionGuaranteeMillis = mService.getMinJobExecutionGuaranteeMs(job);
             mMaxExecutionTimeMillis =
@@ -507,37 +504,6 @@
         }
     }
 
-    private boolean canGetNetworkInformation(@NonNull JobStatus job) {
-        if (job.getJob().getRequiredNetwork() == null) {
-            // The job never had a network constraint, so we're not going to give it a network
-            // object. Add this check as an early return to avoid wasting cycles doing permission
-            // checks for this job.
-            return false;
-        }
-        // The calling app is doing the work, so use its UID, not the source UID.
-        final int uid = job.getUid();
-        if (CompatChanges.isChangeEnabled(
-                JobSchedulerService.REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) {
-            final String pkgName = job.getServiceComponent().getPackageName();
-            if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.INTERNET)) {
-                return false;
-            }
-            if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.ACCESS_NETWORK_STATE)) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    private boolean hasPermissionForDelivery(int uid, @NonNull String pkgName,
-            @NonNull String permission) {
-        final int result = PermissionChecker.checkPermissionForDataDelivery(mContext, permission,
-                PermissionChecker.PID_UNKNOWN, uid, pkgName, /* attributionTag */ null,
-                "network info via JS");
-        return result == PermissionChecker.PERMISSION_GRANTED;
-    }
-
     @EconomicPolicy.AppAction
     private static int getStartActionId(@NonNull JobStatus job) {
         switch (job.getEffectivePriority()) {
@@ -637,15 +603,6 @@
     }
 
     void informOfNetworkChangeLocked(Network newNetwork) {
-        if (newNetwork != null && mRunningJob != null && !canGetNetworkInformation(mRunningJob)) {
-            // The app can't get network information, so there's no point informing it of network
-            // changes. This case may happen if an app had scheduled network job and then
-            // started targeting U+ without requesting the required network permissions.
-            if (DEBUG) {
-                Slog.d(TAG, "Skipping network change call because of missing permissions");
-            }
-            return;
-        }
         if (mVerb != VERB_EXECUTING) {
             Slog.w(TAG, "Sending onNetworkChanged for a job that isn't started. " + mRunningJob);
             if (mVerb == VERB_BINDING || mVerb == VERB_STARTING) {