Don't run jobs when connectivity is not validated

Previously, the job scheduler would run jobs with connectivity
constraints as soon as "raw" connectivity was achieved.  This could
cause problems, though, because the device might be behind a
captive portal or similar, so apps expecting to contact the internet
would still see connect() failures or other unexpected results.

We now run such jobs only when connectivity has been validated,
so jobs have a stronger expectation that their needed internet
access will be possible.

Bug 28346529
Bug 36137931
Test: manual; use tethered phone hotspot, both with and without SIM,
      and verify that the 'validation' trigger is correct.

Change-Id: I79194467610ef9611d38f871d01f92044d0ee9de
This commit is contained in:
Christopher Tate
2017-03-15 11:07:46 -07:00
parent 474662da39
commit 8bbe704273

View File

@@ -17,18 +17,18 @@
package com.android.server.job.controllers;
import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.INetworkPolicyListener;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkPolicyManager;
import android.os.Process;
import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.os.BackgroundThread;
import com.android.internal.annotations.GuardedBy;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
@@ -46,9 +46,12 @@ import java.util.ArrayList;
public class ConnectivityController extends StateController implements
ConnectivityManager.OnNetworkActiveListener {
private static final String TAG = "JobScheduler.Conn";
private static final boolean DEBUG = false;
private final ConnectivityManager mConnManager;
private final NetworkPolicyManager mNetPolicyManager;
private boolean mConnected;
private boolean mValidated;
@GuardedBy("mLock")
private final ArrayList<JobStatus> mTrackedJobs = new ArrayList<JobStatus>();
@@ -73,10 +76,9 @@ public class ConnectivityController extends StateController implements
mConnManager = mContext.getSystemService(ConnectivityManager.class);
mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
final IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiverAsUser(
mConnectivityReceiver, UserHandle.SYSTEM, intentFilter, null, null);
mConnected = mValidated = false;
mConnManager.registerDefaultNetworkCallback(mNetworkCallback);
mNetPolicyManager.registerListener(mNetPolicyListener);
}
@@ -84,7 +86,7 @@ public class ConnectivityController extends StateController implements
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()
|| jobStatus.hasNotRoamingConstraint()) {
updateConstraintsSatisfied(jobStatus);
updateConstraintsSatisfied(jobStatus, null);
mTrackedJobs.add(jobStatus);
}
}
@@ -98,18 +100,43 @@ public class ConnectivityController extends StateController implements
}
}
private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
private boolean updateConstraintsSatisfied(JobStatus jobStatus,
NetworkCapabilities capabilities) {
final int jobUid = jobStatus.getSourceUid();
final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobStatus.getSourceUid(),
ignoreBlocked);
final boolean connected = (info != null) && info.isConnected();
final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobUid, ignoreBlocked);
if (capabilities == null) {
final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
capabilities = mConnManager.getNetworkCapabilities(network);
}
final boolean validated = capabilities != null
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
final boolean connected = info != null && info.isConnected();
final boolean connectionUsable = connected && validated;
final boolean unmetered = connected && !info.isMetered();
final boolean notRoaming = connected && !info.isRoaming();
boolean changed = false;
changed |= jobStatus.setConnectivityConstraintSatisfied(connected);
changed |= jobStatus.setConnectivityConstraintSatisfied(connectionUsable);
changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered);
changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming);
// Track system-uid connected/validated as a general reportable proxy for the
// overall state of connectivity constraint satisfiability.
if (jobUid == Process.SYSTEM_UID) {
mConnected = connected;
mValidated = validated;
}
if (DEBUG) {
Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
+ " for " + jobStatus + ": usable=" + connectionUsable
+ " connected=" + connected
+ " validated=" + validated
+ " unmetered=" + unmetered
+ " notRoaming=" + notRoaming);
}
return changed;
}
@@ -119,13 +146,13 @@ public class ConnectivityController extends StateController implements
* @param uid only update jobs belonging to this UID, or {@code -1} to
* update all tracked jobs.
*/
private void updateTrackedJobs(int uid) {
private void updateTrackedJobs(int uid, NetworkCapabilities capabilities) {
synchronized (mLock) {
boolean changed = false;
for (int i = 0; i < mTrackedJobs.size(); i++) {
final JobStatus js = mTrackedJobs.get(i);
if (uid == -1 || uid == js.getSourceUid()) {
changed |= updateConstraintsSatisfied(js);
changed |= updateConstraintsSatisfied(js, capabilities);
}
}
if (changed) {
@@ -152,38 +179,61 @@ public class ConnectivityController extends StateController implements
}
}
private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
public void onReceive(Context context, Intent intent) {
updateTrackedJobs(-1);
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
if (DEBUG) {
Slog.v(TAG, "onCapabilitiesChanged() : " + networkCapabilities);
}
updateTrackedJobs(-1, networkCapabilities);
}
@Override
public void onLost(Network network) {
if (DEBUG) {
Slog.v(TAG, "Network lost");
}
updateTrackedJobs(-1, null);
}
};
private INetworkPolicyListener mNetPolicyListener = new INetworkPolicyListener.Stub() {
private final INetworkPolicyListener mNetPolicyListener = new INetworkPolicyListener.Stub() {
@Override
public void onUidRulesChanged(int uid, int uidRules) {
updateTrackedJobs(uid);
if (DEBUG) {
Slog.v(TAG, "Uid rules changed for " + uid);
}
updateTrackedJobs(uid, null);
}
@Override
public void onMeteredIfacesChanged(String[] meteredIfaces) {
updateTrackedJobs(-1);
// We track this via our NetworkCallback
}
@Override
public void onRestrictBackgroundChanged(boolean restrictBackground) {
updateTrackedJobs(-1);
if (DEBUG) {
Slog.v(TAG, "Background restriction change to " + restrictBackground);
}
updateTrackedJobs(-1, null);
}
@Override
public void onUidPoliciesChanged(int uid, int uidPolicies) {
updateTrackedJobs(uid);
if (DEBUG) {
Slog.v(TAG, "Uid policy changed for " + uid);
}
updateTrackedJobs(uid, null);
}
};
@Override
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
pw.println("Connectivity.");
pw.print("Connectivity: connected=");
pw.print(mConnected);
pw.print(" validated=");
pw.println(mValidated);
pw.print("Tracking ");
pw.print(mTrackedJobs.size());
pw.println(":");