Merge "Revert "Require network permission for networked jobs."" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
7871be0c97
@@ -1382,12 +1382,6 @@ public class JobInfo implements Parcelable {
|
||||
* 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 @@ public class JobInfo implements Parcelable {
|
||||
* 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}
|
||||
|
||||
@@ -481,10 +481,6 @@ public class JobParameters implements Parcelable {
|
||||
* 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.
|
||||
|
||||
@@ -23,7 +23,6 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||
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 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
@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 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
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 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
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 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
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 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
}
|
||||
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?
|
||||
@@ -3778,39 +3747,19 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
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 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
+ " 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;
|
||||
|
||||
@@ -21,7 +21,6 @@ import static android.app.job.JobInfo.getPriorityString;
|
||||
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.compat.annotation.EnabledAfter;
|
||||
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 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
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 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user