diff options
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); |