summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kweku Adams <kwekua@google.com> 2023-01-09 18:07:16 +0000
committer Kweku Adams <kwekua@google.com> 2023-01-12 19:42:10 +0000
commitf28d7efdb6746f6f438c193e40dfd74cab2d7ec6 (patch)
treef7f306a42d4a63726a84aff80e25dfdb05a6c845
parentb3fb718c2d8670c2871b942181f71de67b23a6c1 (diff)
Inform apps of network changes.
Inform apps when they network JS thinks they should use changes so they can attempt to switch over without tracking network changes themselves. Bug: 152942222 Test: atest CtsJobSchedulerTestCases:ConnectivityConstraintTest Change-Id: Ia6ddcba88ef89b217bfcaf5aeb734b33a59d35d3
-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);