From 8bbe7042739e4e047dfc1a1f6d6b48e6694c1927 Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Wed, 15 Mar 2017 11:07:46 -0700 Subject: [PATCH] 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 --- .../controllers/ConnectivityController.java | 98 ++++++++++++++----- 1 file changed, 74 insertions(+), 24 deletions(-) diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index 94ca24c9db9f2..b458d8b72adef 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -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 mTrackedJobs = new ArrayList(); @@ -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(":");