summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/IJobService.aidl2
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobParameters.java20
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobService.java27
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java31
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java19
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java12
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java31
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java15
-rw-r--r--core/api/current.txt2
10 files changed, 153 insertions, 9 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
index 2bb82bd006de..f8dc3b01162c 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobService.aidl
@@ -32,6 +32,8 @@ oneway interface IJobService {
/** Stop execution of application's job. */
@UnsupportedAppUsage
void stopJob(in JobParameters jobParams);
+ /** Inform the job of a change in the network it should use. */
+ void onNetworkChanged(in JobParameters jobParams);
/** Update JS of how much data has been downloaded. */
void getTransferredDownloadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
/** Update JS of how much data has been uploaded. */
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 18ddffbaf885..f6c43e787ac9 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -290,7 +290,8 @@ public class JobParameters implements Parcelable {
private final boolean mIsUserInitiated;
private final Uri[] mTriggeredContentUris;
private final String[] mTriggeredContentAuthorities;
- private final Network network;
+ @Nullable
+ private Network mNetwork;
private int mStopReason = STOP_REASON_UNDEFINED;
private int mInternalStopReason = INTERNAL_STOP_REASON_UNKNOWN;
@@ -313,7 +314,7 @@ public class JobParameters implements Parcelable {
this.mIsUserInitiated = isUserInitiated;
this.mTriggeredContentUris = triggeredContentUris;
this.mTriggeredContentAuthorities = triggeredContentAuthorities;
- this.network = network;
+ this.mNetwork = network;
this.mJobNamespace = namespace;
}
@@ -478,7 +479,7 @@ public class JobParameters implements Parcelable {
* @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
*/
public @Nullable Network getNetwork() {
- return network;
+ return mNetwork;
}
/**
@@ -573,9 +574,9 @@ public class JobParameters implements Parcelable {
mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
mTriggeredContentAuthorities = in.createStringArray();
if (in.readInt() != 0) {
- network = Network.CREATOR.createFromParcel(in);
+ mNetwork = Network.CREATOR.createFromParcel(in);
} else {
- network = null;
+ mNetwork = null;
}
mStopReason = in.readInt();
mInternalStopReason = in.readInt();
@@ -583,6 +584,11 @@ public class JobParameters implements Parcelable {
}
/** @hide */
+ public void setNetwork(@Nullable Network network) {
+ mNetwork = network;
+ }
+
+ /** @hide */
public void setStopReason(@StopReason int reason, int internalStopReason,
String debugStopReason) {
mStopReason = reason;
@@ -614,9 +620,9 @@ public class JobParameters implements Parcelable {
dest.writeBoolean(mIsUserInitiated);
dest.writeTypedArray(mTriggeredContentUris, flags);
dest.writeStringArray(mTriggeredContentAuthorities);
- if (network != null) {
+ if (mNetwork != null) {
dest.writeInt(1);
- network.writeToParcel(dest, flags);
+ mNetwork.writeToParcel(dest, flags);
} else {
dest.writeInt(0);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 449c316c1c65..31d2266feab4 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -27,6 +27,7 @@ import android.app.Service;
import android.compat.Compatibility;
import android.content.Intent;
import android.os.IBinder;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -131,6 +132,11 @@ public abstract class JobService extends Service {
return JobService.this.getTransferredUploadBytes(params, item);
}
}
+
+ @Override
+ public void onNetworkChanged(@NonNull JobParameters params) {
+ JobService.this.onNetworkChanged(params);
+ }
};
}
return mEngine.getBinder();
@@ -232,6 +238,27 @@ public abstract class JobService extends Service {
public abstract boolean onStopJob(JobParameters params);
/**
+ * This method is called that for a job that has a network constraint when the network
+ * to be used by the job changes. The new network object will be available via
+ * {@link JobParameters#getNetwork()}. Any network that results in this method call will
+ * match the job's requested network constraints.
+ *
+ * <p>
+ * For example, if a device is on a metered mobile network and then connects to an
+ * unmetered WiFi network, and the job has indicated that both networks satisfy its
+ * network constraint, then this method will be called to notify the job of the new
+ * unmetered WiFi network.
+ *
+ * @param params The parameters identifying this job, similar to what was supplied to the job in
+ * the {@link #onStartJob(JobParameters)} callback, but with an updated network.
+ * @see JobInfo.Builder#setRequiredNetwork(android.net.NetworkRequest)
+ * @see JobInfo.Builder#setRequiredNetworkType(int)
+ */
+ public void onNetworkChanged(@NonNull JobParameters params) {
+ Log.w(TAG, "onNetworkChanged() not implemented. Must override in a subclass.");
+ }
+
+ /**
* Update the amount of data this job is estimated to transfer after the job has started.
*
* @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 697641ef65eb..79d87edff9b2 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -76,6 +76,8 @@ public abstract class JobServiceEngine {
private static final int MSG_UPDATE_ESTIMATED_NETWORK_BYTES = 6;
/** Message that the client wants to give JobScheduler a notification to tie to the job. */
private static final int MSG_SET_NOTIFICATION = 7;
+ /** Message that the network to use has changed. */
+ private static final int MSG_INFORM_OF_NETWORK_CHANGE = 8;
private final IJobService mBinder;
@@ -128,6 +130,16 @@ public abstract class JobServiceEngine {
}
@Override
+ public void onNetworkChanged(JobParameters jobParams) throws RemoteException {
+ JobServiceEngine service = mService.get();
+ if (service != null) {
+ service.mHandler.removeMessages(MSG_INFORM_OF_NETWORK_CHANGE);
+ service.mHandler.obtainMessage(MSG_INFORM_OF_NETWORK_CHANGE, jobParams)
+ .sendToTarget();
+ }
+ }
+
+ @Override
public void stopJob(JobParameters jobParams) throws RemoteException {
JobServiceEngine service = mService.get();
if (service != null) {
@@ -271,6 +283,16 @@ public abstract class JobServiceEngine {
args.recycle();
break;
}
+ case MSG_INFORM_OF_NETWORK_CHANGE: {
+ final JobParameters params = (JobParameters) msg.obj;
+ try {
+ JobServiceEngine.this.onNetworkChanged(params);
+ } catch (Exception e) {
+ Log.e(TAG, "Error while executing job: " + params.getJobId());
+ throw new RuntimeException(e);
+ }
+ break;
+ }
default:
Log.e(TAG, "Unrecognised message received.");
break;
@@ -386,6 +408,15 @@ public abstract class JobServiceEngine {
}
/**
+ * Engine's report that the network for the job has changed.
+ *
+ * @see JobService#onNetworkChanged(JobParameters)
+ */
+ public void onNetworkChanged(@NonNull JobParameters params) {
+ Log.w(TAG, "onNetworkChanged() not implemented. Must override in a subclass.");
+ }
+
+ /**
* Engine's request to get how much data has been downloaded.
*
* @hide
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index e0e0b4bd62e2..bf8984feb0a7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1179,6 +1179,25 @@ class JobConcurrencyManager {
assignJobsToContextsLocked();
}
+ @Nullable
+ @GuardedBy("mLock")
+ JobServiceContext getRunningJobServiceContextLocked(JobStatus job) {
+ if (!mRunningJobs.contains(job)) {
+ return null;
+ }
+
+ for (int i = 0; i < mActiveServices.size(); i++) {
+ JobServiceContext jsc = mActiveServices.get(i);
+ final JobStatus executing = jsc.getRunningJobLocked();
+ if (executing == job) {
+ return jsc;
+ }
+ }
+ Slog.wtf(TAG, "Couldn't find running job on a context");
+ mRunningJobs.remove(job);
+ return null;
+ }
+
@GuardedBy("mLock")
boolean stopJobOnServiceContextLocked(JobStatus job,
@JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
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 4e52ed352981..671608f0b11a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -61,6 +61,7 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
+import android.net.Network;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
@@ -1940,6 +1941,17 @@ public class JobSchedulerService extends com.android.server.SystemService
}
@Override
+ public void onNetworkChanged(JobStatus jobStatus, Network newNetwork) {
+ synchronized (mLock) {
+ final JobServiceContext jsc =
+ mConcurrencyManager.getRunningJobServiceContextLocked(jobStatus);
+ if (jsc != null) {
+ jsc.informOfNetworkChangeLocked(newNetwork);
+ }
+ }
+ }
+
+ @Override
public void onRestrictedBucketChanged(List<JobStatus> jobs) {
final int len = jobs.size();
if (len == 0) {
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 ce7da8607497..814ca6608659 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -36,6 +36,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.net.Network;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -187,6 +188,8 @@ public final class JobServiceContext implements ServiceConnection {
private int mPendingInternalStopReason;
private String mPendingDebugStopReason;
+ private Network mPendingNetworkChange;
+
// Debugging: reason this job was last stopped.
public String mStoppedReason;
@@ -292,6 +295,7 @@ public final class JobServiceContext implements ServiceConnection {
mRunningJob = job;
mRunningJobWorkType = workType;
mRunningCallback = new JobCallback();
+ mPendingNetworkChange = null;
final boolean isDeadlineExpired =
job.hasDeadlineConstraint() &&
(job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis());
@@ -515,6 +519,28 @@ public final class JobServiceContext implements ServiceConnection {
return Math.max(0, mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis - nowElapsed);
}
+ void informOfNetworkChangeLocked(Network newNetwork) {
+ if (mVerb != VERB_EXECUTING) {
+ Slog.w(TAG, "Sending onNetworkChanged for a job that isn't started. " + mRunningJob);
+ if (mVerb == VERB_BINDING || mVerb == VERB_STARTING) {
+ // The network changed before the job has fully started. Hold the change push
+ // until the job has started executing.
+ mPendingNetworkChange = newNetwork;
+ }
+ return;
+ }
+ try {
+ mParams.setNetwork(newNetwork);
+ mPendingNetworkChange = null;
+ service.onNetworkChanged(mParams);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending onNetworkChanged to client.", e);
+ // The job's host app apparently crashed during the job, so we should reschedule.
+ closeAndCleanupJobLocked(/* reschedule */ true,
+ "host crashed when trying to inform of network change");
+ }
+ }
+
boolean isWithinExecutionGuaranteeTime() {
return sElapsedRealtimeClock.millis()
< mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
@@ -972,6 +998,10 @@ public final class JobServiceContext implements ServiceConnection {
return;
}
scheduleOpTimeOutLocked();
+ if (mPendingNetworkChange != null
+ && !Objects.equals(mParams.getNetwork(), mPendingNetworkChange)) {
+ informOfNetworkChangeLocked(mPendingNetworkChange);
+ }
if (mRunningJob.isUserVisibleJob()) {
mService.informObserversOfUserVisibleJobChange(this, mRunningJob, true);
}
@@ -1225,6 +1255,7 @@ public final class JobServiceContext implements ServiceConnection {
mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
mPendingInternalStopReason = 0;
mPendingDebugStopReason = null;
+ mPendingNetworkChange = null;
removeOpTimeOutLocked();
if (completedJob.isUserVisibleJob()) {
mService.informObserversOfUserVisibleJobChange(this, completedJob, false);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
index 554f152dccfb..50064bde0bbe 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
@@ -18,6 +18,7 @@ package com.android.server.job;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.Network;
import android.util.ArraySet;
import com.android.server.job.controllers.JobStatus;
@@ -57,6 +58,8 @@ public interface StateChangedListener {
public void onDeviceIdleStateChanged(boolean deviceIdle);
+ void onNetworkChanged(JobStatus jobStatus, Network newNetwork);
+
/**
* Called when these jobs are added or removed from the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index b49129145811..16f5c7f7d5b1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -1125,6 +1125,17 @@ public final class ConnectivityController extends RestrictingController implemen
mFlexibilityController.isFlexibilitySatisfiedLocked(jobStatus));
}
+ // Try to handle network transitions in a reasonable manner. See the lengthy note inside
+ // UidDefaultNetworkCallback for more details.
+ if (!changed && satisfied && jobStatus.network != null
+ && mService.isCurrentlyRunningLocked(jobStatus)) {
+ // The job's connectivity constraint continues to be satisfied even though the network
+ // has changed.
+ // Inform the job of the new network so that it can attempt to switch over. This is the
+ // ideal behavior for certain transitions such as going from a metered network to an
+ // unmetered network.
+ mStateChangedListener.onNetworkChanged(jobStatus, network);
+ }
// Pass along the evaluated network for job to use; prevents race
// conditions as default routes change over time, and opens the door to
@@ -1419,8 +1430,8 @@ public final class ConnectivityController extends RestrictingController implemen
// the onBlockedStatusChanged() call, we re-evaluate the job, but keep it running
// (assuming the new network satisfies constraints). The app continues to use the old
// network (if they use the network object provided through JobParameters.getNetwork())
- // because we don't notify them of the default network change. If the old network no
- // longer satisfies requested constraints, then we have a problem. Depending on the order
+ // because we don't notify them of the default network change. If the old network later
+ // stops satisfying requested constraints, then we have a problem. Depending on the order
// of calls, if the per-UID callback gets notified of the network change before the
// general callback gets notified of the capabilities change, then the job's network
// object will point to the new network and we won't stop the job, even though we told it
diff --git a/core/api/current.txt b/core/api/current.txt
index 7ddf0b1c57cc..a00ad6878180 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8626,6 +8626,7 @@ package android.app.job {
ctor public JobService();
method public final void jobFinished(android.app.job.JobParameters, boolean);
method public final android.os.IBinder onBind(android.content.Intent);
+ method public void onNetworkChanged(@NonNull android.app.job.JobParameters);
method public abstract boolean onStartJob(android.app.job.JobParameters);
method public abstract boolean onStopJob(android.app.job.JobParameters);
method public final void setNotification(@NonNull android.app.job.JobParameters, int, @NonNull android.app.Notification, int);
@@ -8640,6 +8641,7 @@ package android.app.job {
ctor public JobServiceEngine(android.app.Service);
method public final android.os.IBinder getBinder();
method public void jobFinished(android.app.job.JobParameters, boolean);
+ method public void onNetworkChanged(@NonNull android.app.job.JobParameters);
method public abstract boolean onStartJob(android.app.job.JobParameters);
method public abstract boolean onStopJob(android.app.job.JobParameters);
method public void setNotification(@NonNull android.app.job.JobParameters, int, @NonNull android.app.Notification, int);