Migrate to the asynchronous version of getActiveNetworkForUid.
Register a network callback to get the active network for each UID that schedules a job with a network constraint. Using the asynchronous version means that JS no longer has to call any synchronous network APIs. The networking stack has a hard limit on the number of callbacks that can be scheduled, so we try to prioritize the UIDs that we register callbacks for based on certain characteristics. Bug: 134978280 Bug: 165835257 Bug: 179501234 Bug: 183089482 Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job Test: atest CtsJobSchedulerTestCases Change-Id: Ie93cd22b11cc28135a385c0cf6ad6b577b61d1b7
This commit is contained in:
@@ -30,7 +30,6 @@ 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.net.NetworkRequest;
|
||||
import android.os.Handler;
|
||||
@@ -43,8 +42,10 @@ import android.util.ArraySet;
|
||||
import android.util.DataUnit;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Log;
|
||||
import android.util.Pools;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TimeUtils;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
@@ -55,6 +56,9 @@ import com.android.server.job.JobSchedulerService.Constants;
|
||||
import com.android.server.job.StateControllerProto;
|
||||
import com.android.server.net.NetworkPolicyManagerInternal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -73,6 +77,14 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
private static final boolean DEBUG = JobSchedulerService.DEBUG
|
||||
|| Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
// The networking stack has a hard limit so we can't make this configurable.
|
||||
private static final int MAX_NETWORK_CALLBACKS = 50;
|
||||
/**
|
||||
* Minimum amount of time that should have elapsed before we'll update a {@link UidStats}
|
||||
* instance.
|
||||
*/
|
||||
private static final long MIN_STATS_UPDATE_INTERVAL_MS = 30_000L;
|
||||
|
||||
private final ConnectivityManager mConnManager;
|
||||
private final NetworkPolicyManager mNetPolicyManager;
|
||||
private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;
|
||||
@@ -95,6 +107,69 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
@GuardedBy("mLock")
|
||||
private final ArrayMap<Network, NetworkCapabilities> mAvailableNetworks = new ArrayMap<>();
|
||||
|
||||
private final SparseArray<UidDefaultNetworkCallback> mCurrentDefaultNetworkCallbacks =
|
||||
new SparseArray<>();
|
||||
private final Comparator<UidStats> mUidStatsComparator = new Comparator<UidStats>() {
|
||||
private int prioritizeExistence(int v1, int v2) {
|
||||
if (v1 > 0 && v2 > 0) {
|
||||
return 0;
|
||||
}
|
||||
return v2 - v1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(UidStats us1, UidStats us2) {
|
||||
// TODO: build a better prioritization scheme
|
||||
// Some things to use:
|
||||
// * Proc state
|
||||
// * IMPORTANT_WHILE_IN_FOREGROUND bit
|
||||
final int runningPriority = prioritizeExistence(us1.numRunning, us2.numRunning);
|
||||
if (runningPriority != 0) {
|
||||
return runningPriority;
|
||||
}
|
||||
// Prioritize any UIDs that have jobs that would be ready ahead of UIDs that don't.
|
||||
final int readyWithConnPriority =
|
||||
prioritizeExistence(us1.numReadyWithConnectivity, us2.numReadyWithConnectivity);
|
||||
if (readyWithConnPriority != 0) {
|
||||
return readyWithConnPriority;
|
||||
}
|
||||
// They both have jobs that would be ready. Prioritize the UIDs whose requested
|
||||
// network is available ahead of UIDs that don't have their requested network available.
|
||||
final int reqAvailPriority = prioritizeExistence(
|
||||
us1.numRequestedNetworkAvailable, us2.numRequestedNetworkAvailable);
|
||||
if (reqAvailPriority != 0) {
|
||||
return reqAvailPriority;
|
||||
}
|
||||
// They both have jobs with available networks. Prioritize based on:
|
||||
// 1. (eventually) proc state
|
||||
// 2. Existence of runnable EJs (not just requested)
|
||||
// 3. Enqueue time
|
||||
// TODO: maybe consider number of jobs
|
||||
final int ejPriority = prioritizeExistence(us1.numEJs, us2.numEJs);
|
||||
if (ejPriority != 0) {
|
||||
return ejPriority;
|
||||
}
|
||||
// They both have EJs. Order them by EJ enqueue time to help provide low EJ latency.
|
||||
if (us1.earliestEJEnqueueTime < us2.earliestEJEnqueueTime) {
|
||||
return -1;
|
||||
} else if (us1.earliestEJEnqueueTime > us2.earliestEJEnqueueTime) {
|
||||
return 1;
|
||||
}
|
||||
if (us1.earliestEnqueueTime < us2.earliestEnqueueTime) {
|
||||
return -1;
|
||||
}
|
||||
return us1.earliestEnqueueTime > us2.earliestEnqueueTime ? 1 : 0;
|
||||
}
|
||||
};
|
||||
private final SparseArray<UidStats> mUidStats = new SparseArray<>();
|
||||
private final Pools.Pool<UidDefaultNetworkCallback> mDefaultNetworkCallbackPool =
|
||||
new Pools.SimplePool<>(MAX_NETWORK_CALLBACKS);
|
||||
/**
|
||||
* List of UidStats, sorted by priority as defined in {@link #mUidStatsComparator}. The sorting
|
||||
* is only done in {@link #maybeAdjustRegisteredCallbacksLocked()} and may sometimes be stale.
|
||||
*/
|
||||
private final List<UidStats> mSortedStats = new ArrayList<>();
|
||||
|
||||
private static final int MSG_DATA_SAVER_TOGGLED = 0;
|
||||
private static final int MSG_UID_RULES_CHANGES = 1;
|
||||
private static final int MSG_REEVALUATE_JOBS = 2;
|
||||
@@ -121,7 +196,14 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
@Override
|
||||
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
|
||||
if (jobStatus.hasConnectivityConstraint()) {
|
||||
updateConstraintsSatisfied(jobStatus);
|
||||
UidStats uidStats = mUidStats.get(jobStatus.getSourceUid());
|
||||
if (uidStats == null) {
|
||||
uidStats = new UidStats(jobStatus.getSourceUid());
|
||||
mUidStats.append(jobStatus.getSourceUid(), uidStats);
|
||||
}
|
||||
if (wouldBeReadyWithConstraintLocked(jobStatus, JobStatus.CONSTRAINT_CONNECTIVITY)) {
|
||||
uidStats.numReadyWithConnectivity++;
|
||||
}
|
||||
ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
|
||||
if (jobs == null) {
|
||||
jobs = new ArraySet<>();
|
||||
@@ -129,6 +211,16 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
}
|
||||
jobs.add(jobStatus);
|
||||
jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY);
|
||||
updateConstraintsSatisfied(jobStatus);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Override
|
||||
public void prepareForExecutionLocked(JobStatus jobStatus) {
|
||||
if (jobStatus.hasConnectivityConstraint()) {
|
||||
UidStats uidStats = mUidStats.get(jobStatus.getSourceUid());
|
||||
uidStats.numRunning++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +233,13 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
if (jobs != null) {
|
||||
jobs.remove(jobStatus);
|
||||
}
|
||||
UidStats us = mUidStats.get(jobStatus.getSourceUid());
|
||||
us.numReadyWithConnectivity--;
|
||||
if (jobStatus.madeActive != 0) {
|
||||
us.numRunning--;
|
||||
}
|
||||
maybeRevokeStandbyExceptionLocked(jobStatus);
|
||||
maybeAdjustRegisteredCallbacksLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,6 +327,8 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
return;
|
||||
}
|
||||
|
||||
UidStats uidStats = mUidStats.get(jobStatus.getSourceUid());
|
||||
|
||||
if (jobStatus.shouldTreatAsExpeditedJob()) {
|
||||
if (!jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)) {
|
||||
// Don't request a direct hole through any of the firewalls. Instead, mark the
|
||||
@@ -252,11 +352,15 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would be ready.");
|
||||
}
|
||||
uidStats.numReadyWithConnectivity++;
|
||||
requestStandbyExceptionLocked(jobStatus);
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would not be ready.");
|
||||
}
|
||||
// Don't decrement numReadyWithConnectivity here because we don't know if it was
|
||||
// incremented for this job. The count will be set properly in
|
||||
// maybeAdjustRegisteredCallbacksLocked().
|
||||
maybeRevokeStandbyExceptionLocked(jobStatus);
|
||||
}
|
||||
}
|
||||
@@ -316,6 +420,30 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
@Override
|
||||
public void onAppRemovedLocked(String pkgName, int uid) {
|
||||
mTrackedJobs.delete(uid);
|
||||
UidStats uidStats = mUidStats.removeReturnOld(uid);
|
||||
unregisterDefaultNetworkCallbackLocked(uid, sElapsedRealtimeClock.millis());
|
||||
mSortedStats.remove(uidStats);
|
||||
registerPendingUidCallbacksLocked();
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Override
|
||||
public void onUserRemovedLocked(int userId) {
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
for (int u = mUidStats.size() - 1; u >= 0; --u) {
|
||||
UidStats uidStats = mUidStats.valueAt(u);
|
||||
if (UserHandle.getUserId(uidStats.uid) == userId) {
|
||||
unregisterDefaultNetworkCallbackLocked(uidStats.uid, nowElapsed);
|
||||
mSortedStats.remove(uidStats);
|
||||
mUidStats.removeAt(u);
|
||||
}
|
||||
}
|
||||
maybeAdjustRegisteredCallbacksLocked();
|
||||
}
|
||||
|
||||
private boolean isUsable(NetworkCapabilities capabilities) {
|
||||
return capabilities != null
|
||||
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -428,6 +556,8 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
// Zeroth, we gotta have a network to think about being satisfied
|
||||
if (network == null || capabilities == null) return false;
|
||||
|
||||
if (!isUsable(capabilities)) return false;
|
||||
|
||||
// First, are we insane?
|
||||
if (isInsane(jobStatus, network, capabilities, constants)) return false;
|
||||
|
||||
@@ -443,6 +573,156 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
return false;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void maybeRegisterDefaultNetworkCallbackLocked(JobStatus jobStatus) {
|
||||
final int sourceUid = jobStatus.getSourceUid();
|
||||
if (mCurrentDefaultNetworkCallbacks.contains(sourceUid)) {
|
||||
return;
|
||||
}
|
||||
UidStats uidStats = mUidStats.get(sourceUid);
|
||||
if (!mSortedStats.contains(uidStats)) {
|
||||
mSortedStats.add(uidStats);
|
||||
}
|
||||
if (mCurrentDefaultNetworkCallbacks.size() >= MAX_NETWORK_CALLBACKS) {
|
||||
// TODO: offload to handler
|
||||
maybeAdjustRegisteredCallbacksLocked();
|
||||
return;
|
||||
}
|
||||
registerPendingUidCallbacksLocked();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register UID callbacks for UIDs that are next in line, based on the current order in {@link
|
||||
* #mSortedStats}. This assumes that there are only registered callbacks for UIDs in the top
|
||||
* {@value #MAX_NETWORK_CALLBACKS} UIDs and that the only UIDs missing callbacks are the lower
|
||||
* priority ones.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private void registerPendingUidCallbacksLocked() {
|
||||
final int numCallbacks = mCurrentDefaultNetworkCallbacks.size();
|
||||
final int numPending = mSortedStats.size();
|
||||
if (numPending < numCallbacks) {
|
||||
// This means there's a bug in the code >.<
|
||||
Slog.wtf(TAG, "There are more registered callbacks than sorted UIDs: "
|
||||
+ numCallbacks + " vs " + numPending);
|
||||
}
|
||||
for (int i = numCallbacks; i < numPending && i < MAX_NETWORK_CALLBACKS; ++i) {
|
||||
UidStats uidStats = mSortedStats.get(i);
|
||||
UidDefaultNetworkCallback callback = mDefaultNetworkCallbackPool.acquire();
|
||||
if (callback == null) {
|
||||
callback = new UidDefaultNetworkCallback();
|
||||
}
|
||||
callback.setUid(uidStats.uid);
|
||||
mCurrentDefaultNetworkCallbacks.append(uidStats.uid, callback);
|
||||
mConnManager.registerDefaultNetworkCallbackAsUid(uidStats.uid, callback, mHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void maybeAdjustRegisteredCallbacksLocked() {
|
||||
final int count = mUidStats.size();
|
||||
if (count == mCurrentDefaultNetworkCallbacks.size()) {
|
||||
// All of them are registered and there are no blocked UIDs.
|
||||
// No point evaluating all UIDs.
|
||||
return;
|
||||
}
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
mSortedStats.clear();
|
||||
|
||||
for (int u = 0; u < mUidStats.size(); ++u) {
|
||||
UidStats us = mUidStats.valueAt(u);
|
||||
ArraySet<JobStatus> jobs = mTrackedJobs.get(us.uid);
|
||||
if (jobs == null || jobs.size() == 0) {
|
||||
unregisterDefaultNetworkCallbackLocked(us.uid, nowElapsed);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We won't evaluate stats in the first 30 seconds after boot...That's probably okay.
|
||||
if (us.lastUpdatedElapsed + MIN_STATS_UPDATE_INTERVAL_MS < nowElapsed) {
|
||||
us.earliestEnqueueTime = Long.MAX_VALUE;
|
||||
us.earliestEJEnqueueTime = Long.MAX_VALUE;
|
||||
us.numReadyWithConnectivity = 0;
|
||||
us.numRequestedNetworkAvailable = 0;
|
||||
us.numRegular = 0;
|
||||
us.numEJs = 0;
|
||||
|
||||
for (int j = 0; j < jobs.size(); ++j) {
|
||||
JobStatus job = jobs.valueAt(j);
|
||||
us.earliestEnqueueTime = Math.min(us.earliestEnqueueTime, job.enqueueTime);
|
||||
if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_CONNECTIVITY)) {
|
||||
us.numReadyWithConnectivity++;
|
||||
if (isNetworkAvailable(job)) {
|
||||
us.numRequestedNetworkAvailable++;
|
||||
}
|
||||
}
|
||||
if (job.shouldTreatAsExpeditedJob() || job.startedAsExpeditedJob) {
|
||||
us.numEJs++;
|
||||
us.earliestEJEnqueueTime =
|
||||
Math.min(us.earliestEJEnqueueTime, job.enqueueTime);
|
||||
} else {
|
||||
us.numRegular++;
|
||||
}
|
||||
}
|
||||
|
||||
us.lastUpdatedElapsed = nowElapsed;
|
||||
}
|
||||
mSortedStats.add(us);
|
||||
}
|
||||
|
||||
mSortedStats.sort(mUidStatsComparator);
|
||||
|
||||
boolean changed = false;
|
||||
// Iterate in reverse order to remove existing callbacks before adding new ones.
|
||||
for (int i = mSortedStats.size() - 1; i >= 0; --i) {
|
||||
UidStats us = mSortedStats.get(i);
|
||||
if (i >= MAX_NETWORK_CALLBACKS) {
|
||||
changed |= unregisterDefaultNetworkCallbackLocked(us.uid, nowElapsed);
|
||||
} else {
|
||||
UidDefaultNetworkCallback defaultNetworkCallback =
|
||||
mCurrentDefaultNetworkCallbacks.get(us.uid);
|
||||
if (defaultNetworkCallback == null) {
|
||||
// Not already registered.
|
||||
defaultNetworkCallback = mDefaultNetworkCallbackPool.acquire();
|
||||
if (defaultNetworkCallback == null) {
|
||||
defaultNetworkCallback = new UidDefaultNetworkCallback();
|
||||
}
|
||||
defaultNetworkCallback.setUid(us.uid);
|
||||
mCurrentDefaultNetworkCallbacks.append(us.uid, defaultNetworkCallback);
|
||||
mConnManager.registerDefaultNetworkCallbackAsUid(
|
||||
us.uid, defaultNetworkCallback, mHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
mStateChangedListener.onControllerStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean unregisterDefaultNetworkCallbackLocked(int uid, long nowElapsed) {
|
||||
UidDefaultNetworkCallback defaultNetworkCallback = mCurrentDefaultNetworkCallbacks.get(uid);
|
||||
if (defaultNetworkCallback == null) {
|
||||
return false;
|
||||
}
|
||||
mCurrentDefaultNetworkCallbacks.remove(uid);
|
||||
mConnManager.unregisterNetworkCallback(defaultNetworkCallback);
|
||||
mDefaultNetworkCallbackPool.release(defaultNetworkCallback);
|
||||
defaultNetworkCallback.clear();
|
||||
|
||||
boolean changed = false;
|
||||
final ArraySet<JobStatus> jobs = mTrackedJobs.get(uid);
|
||||
if (jobs != null) {
|
||||
// Since we're unregistering the callback, we can no longer monitor
|
||||
// changes to the app's network and so we should just mark the
|
||||
// connectivity constraint as not satisfied.
|
||||
for (int j = jobs.size() - 1; j >= 0; --j) {
|
||||
changed |= updateConstraintsSatisfied(
|
||||
jobs.valueAt(j), nowElapsed, null, null);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
|
||||
if (network == null) {
|
||||
@@ -451,43 +731,34 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
synchronized (mLock) {
|
||||
// There is technically a race here if the Network object is reused. This can happen
|
||||
// only if that Network disconnects and the auto-incrementing network ID in
|
||||
// ConnectivityService wraps. This should no longer be a concern if/when we only make
|
||||
// ConnectivityService wraps. This shouldn't be a concern since we only make
|
||||
// use of asynchronous calls.
|
||||
if (mAvailableNetworks.get(network) != null) {
|
||||
return mAvailableNetworks.get(network);
|
||||
}
|
||||
|
||||
// This should almost never happen because any time a new network connects, the
|
||||
// NetworkCallback would populate mAvailableNetworks. However, it's currently necessary
|
||||
// because we also call synchronous methods such as getActiveNetworkForUid.
|
||||
// TODO(134978280): remove after switching to callback-based APIs
|
||||
final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
|
||||
mAvailableNetworks.put(network, capabilities);
|
||||
return capabilities;
|
||||
return mAvailableNetworks.get(network);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
|
||||
final Network network = mConnManager.getActiveNetworkForUid(
|
||||
jobStatus.getSourceUid(), jobStatus.shouldIgnoreNetworkBlocking());
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
final UidDefaultNetworkCallback defaultNetworkCallback =
|
||||
mCurrentDefaultNetworkCallbacks.get(jobStatus.getSourceUid());
|
||||
if (defaultNetworkCallback == null) {
|
||||
maybeRegisterDefaultNetworkCallbackLocked(jobStatus);
|
||||
return updateConstraintsSatisfied(jobStatus, nowElapsed, null, null);
|
||||
}
|
||||
final Network network =
|
||||
(jobStatus.shouldIgnoreNetworkBlocking() || !defaultNetworkCallback.mBlocked)
|
||||
? defaultNetworkCallback.mDefaultNetwork : null;
|
||||
final NetworkCapabilities capabilities = getNetworkCapabilities(network);
|
||||
return updateConstraintsSatisfied(jobStatus, sElapsedRealtimeClock.millis(),
|
||||
network, capabilities);
|
||||
return updateConstraintsSatisfied(jobStatus, nowElapsed, network, capabilities);
|
||||
}
|
||||
|
||||
private boolean updateConstraintsSatisfied(JobStatus jobStatus, final long nowElapsed,
|
||||
Network network, NetworkCapabilities capabilities) {
|
||||
// TODO: consider matching against non-active networks
|
||||
// TODO: consider matching against non-default networks
|
||||
|
||||
final boolean ignoreBlocked = jobStatus.shouldIgnoreNetworkBlocking();
|
||||
final NetworkInfo info = mConnManager.getNetworkInfoForUid(network,
|
||||
jobStatus.getSourceUid(), ignoreBlocked);
|
||||
|
||||
final boolean connected = (info != null) && info.isConnected();
|
||||
final boolean satisfied = isSatisfied(jobStatus, network, capabilities, mConstants);
|
||||
|
||||
final boolean changed = jobStatus
|
||||
.setConnectivityConstraintSatisfied(nowElapsed, connected && satisfied);
|
||||
final boolean changed = jobStatus.setConnectivityConstraintSatisfied(nowElapsed, satisfied);
|
||||
|
||||
// Pass along the evaluated network for job to use; prevents race
|
||||
// conditions as default routes change over time, and opens the door to
|
||||
@@ -496,7 +767,7 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
|
||||
+ " for " + jobStatus + ": connected=" + connected
|
||||
+ " for " + jobStatus + ": usable=" + isUsable(capabilities)
|
||||
+ " satisfied=" + satisfied);
|
||||
}
|
||||
return changed;
|
||||
@@ -531,15 +802,24 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
return false;
|
||||
}
|
||||
|
||||
final Network network =
|
||||
mConnManager.getActiveNetworkForUid(jobs.valueAt(0).getSourceUid(), false);
|
||||
UidDefaultNetworkCallback defaultNetworkCallback =
|
||||
mCurrentDefaultNetworkCallbacks.get(jobs.valueAt(0).getSourceUid());
|
||||
if (defaultNetworkCallback == null) {
|
||||
maybeRegisterDefaultNetworkCallbackLocked(jobs.valueAt(0));
|
||||
return false;
|
||||
}
|
||||
|
||||
final Network network = defaultNetworkCallback.mBlocked
|
||||
? null : defaultNetworkCallback.mDefaultNetwork;
|
||||
final NetworkCapabilities capabilities = getNetworkCapabilities(network);
|
||||
final boolean networkMatch = (filterNetwork == null
|
||||
|| Objects.equals(filterNetwork, network));
|
||||
boolean exemptedLoaded = false;
|
||||
Network exemptedNetwork = null;
|
||||
NetworkCapabilities exemptedNetworkCapabilities = null;
|
||||
boolean exemptedNetworkMatch = false;
|
||||
// Ignore blocked
|
||||
final Network exemptedNetwork = defaultNetworkCallback.mDefaultNetwork;
|
||||
final NetworkCapabilities exemptedNetworkCapabilities =
|
||||
getNetworkCapabilities(exemptedNetwork);
|
||||
final boolean exemptedNetworkMatch =
|
||||
(filterNetwork == null || Objects.equals(filterNetwork, exemptedNetwork));
|
||||
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
boolean changed = false;
|
||||
@@ -551,13 +831,6 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
boolean match = networkMatch;
|
||||
|
||||
if (js.shouldIgnoreNetworkBlocking()) {
|
||||
if (!exemptedLoaded) {
|
||||
exemptedLoaded = true;
|
||||
exemptedNetwork = mConnManager.getActiveNetworkForUid(js.getSourceUid(), true);
|
||||
exemptedNetworkCapabilities = getNetworkCapabilities(exemptedNetwork);
|
||||
exemptedNetworkMatch = (filterNetwork == null
|
||||
|| Objects.equals(filterNetwork, exemptedNetwork));
|
||||
}
|
||||
net = exemptedNetwork;
|
||||
netCap = exemptedNetworkCapabilities;
|
||||
match = exemptedNetworkMatch;
|
||||
@@ -612,6 +885,7 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
mAvailableNetworks.put(network, capabilities);
|
||||
}
|
||||
updateTrackedJobs(-1, network);
|
||||
maybeAdjustRegisteredCallbacksLocked();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -621,8 +895,15 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
}
|
||||
synchronized (mLock) {
|
||||
mAvailableNetworks.remove(network);
|
||||
for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) {
|
||||
UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u);
|
||||
if (Objects.equals(callback.mDefaultNetwork, network)) {
|
||||
callback.mDefaultNetwork = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTrackedJobs(-1, network);
|
||||
maybeAdjustRegisteredCallbacksLocked();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -665,12 +946,142 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class UidDefaultNetworkCallback extends NetworkCallback {
|
||||
private int mUid;
|
||||
@Nullable
|
||||
private Network mDefaultNetwork;
|
||||
private boolean mBlocked;
|
||||
|
||||
private void setUid(int uid) {
|
||||
mUid = uid;
|
||||
mDefaultNetwork = null;
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
mDefaultNetwork = null;
|
||||
mUid = UserHandle.USER_NULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
if (DEBUG) Slog.v(TAG, "default-onAvailable(" + mUid + "): " + network);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockedStatusChanged(Network network, boolean blocked) {
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "default-onBlockedStatusChanged(" + mUid + "): "
|
||||
+ network + " -> " + blocked);
|
||||
}
|
||||
if (mUid == UserHandle.USER_NULL) {
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
mDefaultNetwork = network;
|
||||
mBlocked = blocked;
|
||||
}
|
||||
updateTrackedJobs(mUid, network);
|
||||
}
|
||||
|
||||
// Network transitions have some complicated behavior that JS doesn't handle very well.
|
||||
//
|
||||
// * If the default network changes from A to B without A disconnecting, then we'll only
|
||||
// get onAvailable(B) (and the subsequent onBlockedStatusChanged() call). Since we get
|
||||
// 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
|
||||
// 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
|
||||
// to use the old network that no longer satisfies its constraints. This is the behavior
|
||||
// we loosely had (ignoring race conditions between asynchronous and synchronous
|
||||
// connectivity calls) when we were calling the synchronous getActiveNetworkForUid() API.
|
||||
// However, we should fix it.
|
||||
// TODO: stop jobs when the existing capabilities change after default network change
|
||||
//
|
||||
// * If the default network changes from A to B because A disconnected, then we'll get
|
||||
// onLost(A) and then onAvailable(B). In this case, there will be a short period where JS
|
||||
// doesn't think there's an available network for the job, so we'll stop the job even
|
||||
// though onAvailable(B) will be called soon. One on hand, the app would have gotten a
|
||||
// network error as well because of A's disconnect, and this will allow JS to provide the
|
||||
// job with the new default network. On the other hand, we have to stop the job even
|
||||
// though it could have continued running with the new network and the job has to deal
|
||||
// with whatever backoff policy is set. For now, the current behavior is fine, but we may
|
||||
// want to see if there's a way to have a smoother transition.
|
||||
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "default-onLost(" + mUid + "): " + network);
|
||||
}
|
||||
if (mUid == UserHandle.USER_NULL) {
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
if (Objects.equals(mDefaultNetwork, network)) {
|
||||
mDefaultNetwork = null;
|
||||
}
|
||||
}
|
||||
updateTrackedJobs(mUid, network);
|
||||
}
|
||||
|
||||
private void dumpLocked(IndentingPrintWriter pw) {
|
||||
pw.print("UID: ");
|
||||
pw.print(mUid);
|
||||
pw.print("; ");
|
||||
if (mDefaultNetwork == null) {
|
||||
pw.print("No network");
|
||||
} else {
|
||||
pw.print("Network: ");
|
||||
pw.print(mDefaultNetwork);
|
||||
pw.print(" (blocked=");
|
||||
pw.print(mBlocked);
|
||||
pw.print(")");
|
||||
}
|
||||
pw.println();
|
||||
}
|
||||
}
|
||||
|
||||
private static class UidStats {
|
||||
public final int uid;
|
||||
public int numRunning;
|
||||
public int numReadyWithConnectivity;
|
||||
public int numRequestedNetworkAvailable;
|
||||
public int numEJs;
|
||||
public int numRegular;
|
||||
public long earliestEnqueueTime;
|
||||
public long earliestEJEnqueueTime;
|
||||
public long lastUpdatedElapsed;
|
||||
|
||||
private UidStats(int uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
private void dumpLocked(IndentingPrintWriter pw, final long nowElapsed) {
|
||||
pw.print("UidStats{");
|
||||
pw.print("uid", uid);
|
||||
pw.print("#run", numRunning);
|
||||
pw.print("#readyWithConn", numReadyWithConnectivity);
|
||||
pw.print("#netAvail", numRequestedNetworkAvailable);
|
||||
pw.print("#EJs", numEJs);
|
||||
pw.print("#reg", numRegular);
|
||||
pw.print("earliestEnqueue", earliestEnqueueTime);
|
||||
pw.print("earliestEJEnqueue", earliestEJEnqueueTime);
|
||||
pw.print("updated=");
|
||||
TimeUtils.formatDuration(lastUpdatedElapsed - nowElapsed, pw);
|
||||
pw.println("}");
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Override
|
||||
public void dumpControllerStateLocked(IndentingPrintWriter pw,
|
||||
Predicate<JobStatus> predicate) {
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
|
||||
if (mRequestedWhitelistJobs.size() > 0) {
|
||||
pw.print("Requested standby exceptions:");
|
||||
@@ -695,6 +1106,26 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
} else {
|
||||
pw.println("No available networks");
|
||||
}
|
||||
pw.println();
|
||||
|
||||
pw.println("Current default network callbacks:");
|
||||
pw.increaseIndent();
|
||||
for (int i = 0; i < mCurrentDefaultNetworkCallbacks.size(); i++) {
|
||||
mCurrentDefaultNetworkCallbacks.valueAt(i).dumpLocked(pw);
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
pw.println();
|
||||
|
||||
pw.println("UID Pecking Order:");
|
||||
pw.increaseIndent();
|
||||
for (int i = 0; i < mSortedStats.size(); ++i) {
|
||||
pw.print(i);
|
||||
pw.print(": ");
|
||||
mSortedStats.get(i).dumpLocked(pw, nowElapsed);
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
pw.println();
|
||||
|
||||
for (int i = 0; i < mTrackedJobs.size(); i++) {
|
||||
final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
|
||||
for (int j = 0; j < jobs.size(); j++) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.job.controllers;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
|
||||
@@ -41,10 +42,11 @@ import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.job.JobInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -53,8 +55,6 @@ import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.NetworkInfo.DetailedState;
|
||||
import android.net.NetworkPolicyManager;
|
||||
import android.os.Build;
|
||||
import android.os.Looper;
|
||||
@@ -136,7 +136,7 @@ public class ConnectivityControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsane() throws Exception {
|
||||
public void testUsable() throws Exception {
|
||||
final Network net = new Network(101);
|
||||
final JobInfo.Builder job = createJob()
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
|
||||
@@ -191,6 +191,30 @@ public class ConnectivityControllerTest {
|
||||
.setLinkDownstreamBandwidthKbps(130), mConstants));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsane() throws Exception {
|
||||
final Network net = new Network(101);
|
||||
final JobInfo.Builder job = createJob()
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
|
||||
DataUnit.MEBIBYTES.toBytes(1))
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
|
||||
|
||||
final ConnectivityController controller = new ConnectivityController(mService);
|
||||
when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
|
||||
|
||||
|
||||
// Suspended networks aren't usable.
|
||||
assertFalse(controller.isSatisfied(createJobStatus(job), net,
|
||||
createCapabilities().removeCapability(NET_CAPABILITY_NOT_SUSPENDED)
|
||||
.setLinkUpstreamBandwidthKbps(1024).setLinkDownstreamBandwidthKbps(1024),
|
||||
mConstants));
|
||||
|
||||
// Not suspended networks are usable.
|
||||
assertTrue(controller.isSatisfied(createJobStatus(job), net,
|
||||
createCapabilities().setLinkUpstreamBandwidthKbps(1024)
|
||||
.setLinkDownstreamBandwidthKbps(1024), mConstants));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCongestion() throws Exception {
|
||||
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
|
||||
@@ -263,9 +287,17 @@ public class ConnectivityControllerTest {
|
||||
|
||||
@Test
|
||||
public void testUpdates() throws Exception {
|
||||
final ArgumentCaptor<NetworkCallback> callback = ArgumentCaptor
|
||||
.forClass(NetworkCallback.class);
|
||||
doNothing().when(mConnManager).registerNetworkCallback(any(), callback.capture());
|
||||
final ArgumentCaptor<NetworkCallback> callbackCaptor =
|
||||
ArgumentCaptor.forClass(NetworkCallback.class);
|
||||
doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
|
||||
final ArgumentCaptor<NetworkCallback> redCallbackCaptor =
|
||||
ArgumentCaptor.forClass(NetworkCallback.class);
|
||||
doNothing().when(mConnManager).registerDefaultNetworkCallbackAsUid(
|
||||
eq(UID_RED), redCallbackCaptor.capture(), any());
|
||||
final ArgumentCaptor<NetworkCallback> blueCallbackCaptor =
|
||||
ArgumentCaptor.forClass(NetworkCallback.class);
|
||||
doNothing().when(mConnManager).registerDefaultNetworkCallbackAsUid(
|
||||
eq(UID_BLUE), blueCallbackCaptor.capture(), any());
|
||||
|
||||
final ConnectivityController controller = new ConnectivityController(mService);
|
||||
|
||||
@@ -281,15 +313,16 @@ public class ConnectivityControllerTest {
|
||||
final JobStatus blue = createJobStatus(createJob()
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
|
||||
controller.maybeStartTrackingJobLocked(red, null);
|
||||
controller.maybeStartTrackingJobLocked(blue, null);
|
||||
final NetworkCallback generalCallback = callbackCaptor.getValue();
|
||||
final NetworkCallback redCallback = redCallbackCaptor.getValue();
|
||||
final NetworkCallback blueCallback = blueCallbackCaptor.getValue();
|
||||
|
||||
// Pretend we're offline when job is added
|
||||
{
|
||||
reset(mConnManager);
|
||||
answerNetwork(UID_RED, null, null);
|
||||
answerNetwork(UID_BLUE, null, null);
|
||||
|
||||
controller.maybeStartTrackingJobLocked(red, null);
|
||||
controller.maybeStartTrackingJobLocked(blue, null);
|
||||
answerNetwork(generalCallback, redCallback, null, null, null);
|
||||
answerNetwork(generalCallback, blueCallback, null, null, null);
|
||||
|
||||
assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
assertFalse(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
@@ -297,11 +330,10 @@ public class ConnectivityControllerTest {
|
||||
|
||||
// Metered network
|
||||
{
|
||||
reset(mConnManager);
|
||||
answerNetwork(UID_RED, meteredNet, meteredCaps);
|
||||
answerNetwork(UID_BLUE, meteredNet, meteredCaps);
|
||||
answerNetwork(generalCallback, redCallback, null, meteredNet, meteredCaps);
|
||||
answerNetwork(generalCallback, blueCallback, null, meteredNet, meteredCaps);
|
||||
|
||||
callback.getValue().onCapabilitiesChanged(meteredNet, meteredCaps);
|
||||
generalCallback.onCapabilitiesChanged(meteredNet, meteredCaps);
|
||||
|
||||
assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
@@ -309,11 +341,10 @@ public class ConnectivityControllerTest {
|
||||
|
||||
// Unmetered network background
|
||||
{
|
||||
reset(mConnManager);
|
||||
answerNetwork(UID_RED, meteredNet, meteredCaps);
|
||||
answerNetwork(UID_BLUE, meteredNet, meteredCaps);
|
||||
answerNetwork(generalCallback, redCallback, meteredNet, meteredNet, meteredCaps);
|
||||
answerNetwork(generalCallback, blueCallback, meteredNet, meteredNet, meteredCaps);
|
||||
|
||||
callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
|
||||
generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
|
||||
|
||||
assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
@@ -321,11 +352,10 @@ public class ConnectivityControllerTest {
|
||||
|
||||
// Lost metered network
|
||||
{
|
||||
reset(mConnManager);
|
||||
answerNetwork(UID_RED, unmeteredNet, unmeteredCaps);
|
||||
answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps);
|
||||
answerNetwork(generalCallback, redCallback, meteredNet, unmeteredNet, unmeteredCaps);
|
||||
answerNetwork(generalCallback, blueCallback, meteredNet, unmeteredNet, unmeteredCaps);
|
||||
|
||||
callback.getValue().onLost(meteredNet);
|
||||
generalCallback.onLost(meteredNet);
|
||||
|
||||
assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
@@ -333,11 +363,10 @@ public class ConnectivityControllerTest {
|
||||
|
||||
// Specific UID was blocked
|
||||
{
|
||||
reset(mConnManager);
|
||||
answerNetwork(UID_RED, null, null);
|
||||
answerNetwork(UID_BLUE, unmeteredNet, unmeteredCaps);
|
||||
answerNetwork(generalCallback, redCallback, unmeteredNet, null, null);
|
||||
answerNetwork(generalCallback, blueCallback, unmeteredNet, unmeteredNet, unmeteredCaps);
|
||||
|
||||
callback.getValue().onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
|
||||
generalCallback.onCapabilitiesChanged(unmeteredNet, unmeteredCaps);
|
||||
|
||||
assertFalse(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
@@ -404,6 +433,8 @@ public class ConnectivityControllerTest {
|
||||
final JobStatus blue = createJobStatus(createJob()
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE);
|
||||
controller.maybeStartTrackingJobLocked(red, null);
|
||||
controller.maybeStartTrackingJobLocked(blue, null);
|
||||
|
||||
InOrder inOrder = inOrder(mNetPolicyManagerInternal);
|
||||
|
||||
@@ -560,6 +591,18 @@ public class ConnectivityControllerTest {
|
||||
|
||||
@Test
|
||||
public void testRestrictedJobTracking() {
|
||||
final ArgumentCaptor<NetworkCallback> callback =
|
||||
ArgumentCaptor.forClass(NetworkCallback.class);
|
||||
doNothing().when(mConnManager).registerNetworkCallback(any(), callback.capture());
|
||||
final ArgumentCaptor<NetworkCallback> redCallback =
|
||||
ArgumentCaptor.forClass(NetworkCallback.class);
|
||||
doNothing().when(mConnManager).registerDefaultNetworkCallbackAsUid(
|
||||
eq(UID_RED), redCallback.capture(), any());
|
||||
final ArgumentCaptor<NetworkCallback> blueCallback =
|
||||
ArgumentCaptor.forClass(NetworkCallback.class);
|
||||
doNothing().when(mConnManager).registerDefaultNetworkCallbackAsUid(
|
||||
eq(UID_BLUE), blueCallback.capture(), any());
|
||||
|
||||
final JobStatus networked = createJobStatus(createJob()
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR), UID_RED);
|
||||
@@ -570,13 +613,11 @@ public class ConnectivityControllerTest {
|
||||
final Network cellularNet = new Network(101);
|
||||
final NetworkCapabilities cellularCaps =
|
||||
createCapabilities().addTransportType(TRANSPORT_CELLULAR);
|
||||
reset(mConnManager);
|
||||
answerNetwork(UID_RED, cellularNet, cellularCaps);
|
||||
answerNetwork(UID_BLUE, cellularNet, cellularCaps);
|
||||
|
||||
final ConnectivityController controller = new ConnectivityController(mService);
|
||||
controller.maybeStartTrackingJobLocked(networked, null);
|
||||
controller.maybeStartTrackingJobLocked(unnetworked, null);
|
||||
answerNetwork(callback.getValue(), redCallback.getValue(), null, cellularNet, cellularCaps);
|
||||
|
||||
assertTrue(networked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
@@ -600,18 +641,28 @@ public class ConnectivityControllerTest {
|
||||
assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
|
||||
}
|
||||
|
||||
private void answerNetwork(int uid, Network net, NetworkCapabilities caps) {
|
||||
when(mConnManager.getActiveNetworkForUid(eq(uid), anyBoolean())).thenReturn(net);
|
||||
when(mConnManager.getNetworkCapabilities(eq(net))).thenReturn(caps);
|
||||
if (net != null) {
|
||||
final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, null, null);
|
||||
ni.setDetailedState(DetailedState.CONNECTED, null, null);
|
||||
when(mConnManager.getNetworkInfoForUid(eq(net), eq(uid), anyBoolean())).thenReturn(ni);
|
||||
private void answerNetwork(@NonNull NetworkCallback generalCallback,
|
||||
@Nullable NetworkCallback uidCallback, @Nullable Network lastNetwork,
|
||||
@Nullable Network net, @Nullable NetworkCapabilities caps) {
|
||||
if (net == null) {
|
||||
generalCallback.onLost(lastNetwork);
|
||||
if (uidCallback != null) {
|
||||
uidCallback.onLost(lastNetwork);
|
||||
}
|
||||
} else {
|
||||
generalCallback.onAvailable(net);
|
||||
generalCallback.onCapabilitiesChanged(net, caps);
|
||||
if (uidCallback != null) {
|
||||
uidCallback.onAvailable(net);
|
||||
uidCallback.onBlockedStatusChanged(net, false);
|
||||
uidCallback.onCapabilitiesChanged(net, caps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static NetworkCapabilities createCapabilities() {
|
||||
return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NET_CAPABILITY_NOT_SUSPENDED)
|
||||
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
|
||||
.addCapability(NET_CAPABILITY_VALIDATED);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user