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
This commit is contained in:
@@ -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. */
|
||||
|
||||
@@ -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,15 +574,20 @@ 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();
|
||||
debugStopReason = in.readString();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void setNetwork(@Nullable Network network) {
|
||||
mNetwork = network;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void setStopReason(@StopReason int reason, int internalStopReason,
|
||||
String debugStopReason) {
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -231,6 +237,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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -127,6 +129,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();
|
||||
@@ -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;
|
||||
@@ -385,6 +407,15 @@ public abstract class JobServiceEngine {
|
||||
m.sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
@@ -1939,6 +1940,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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user