diff options
15 files changed, 793 insertions, 309 deletions
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt index 942a3de7a144..95df8b8192a6 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -314,9 +314,16 @@ package android.net { method public int getProviderId(); method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest); method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int); + method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void registerNetworkOffer(@NonNull android.net.NetworkScore, @NonNull android.net.NetworkCapabilities, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkProvider.NetworkOfferCallback); + method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkOffer(@NonNull android.net.NetworkProvider.NetworkOfferCallback); field public static final int ID_NONE = -1; // 0xffffffff } + public static interface NetworkProvider.NetworkOfferCallback { + method public void onNetworkNeeded(@NonNull android.net.NetworkRequest); + method public void onNetworkUnneeded(@NonNull android.net.NetworkRequest); + } + public class NetworkReleasedException extends java.lang.Exception { } @@ -331,14 +338,18 @@ package android.net { public final class NetworkScore implements android.os.Parcelable { method public int describeContents(); + method public int getKeepConnectedReason(); method public int getLegacyInt(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkScore> CREATOR; + field public static final int KEEP_CONNECTED_FOR_HANDOVER = 1; // 0x1 + field public static final int KEEP_CONNECTED_NONE = 0; // 0x0 } public static final class NetworkScore.Builder { ctor public NetworkScore.Builder(); method @NonNull public android.net.NetworkScore build(); + method @NonNull public android.net.NetworkScore.Builder setKeepConnectedReason(int); method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int); } diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index a9f8b8dda340..c9d37adced36 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -3345,7 +3345,7 @@ public class ConnectivityManager { * @param score The prospective score of the network. * @param caps The prospective capabilities of the network. * @param callback The callback to call when this offer is needed or unneeded. - * @hide + * @hide exposed via the NetworkProvider class. */ @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, @@ -3368,7 +3368,7 @@ public class ConnectivityManager { * * @param callback The callback passed at registration time. This must be the same object * that was passed to {@link #offerNetwork} - * @hide + * @hide exposed via the NetworkProvider class. */ public void unofferNetwork(@NonNull final INetworkOfferCallback callback) { try { diff --git a/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl index a6de173fa33e..ecfba21b8ab6 100644 --- a/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl +++ b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl @@ -51,10 +51,8 @@ oneway interface INetworkOfferCallback { /** * Called when a network for this offer is needed to fulfill this request. * @param networkRequest the request to satisfy - * @param providerId the ID of the provider currently satisfying - * this request, or NetworkProvider.ID_NONE if none. */ - void onNetworkNeeded(in NetworkRequest networkRequest, int providerId); + void onNetworkNeeded(in NetworkRequest networkRequest); /** * Informs the registrant that the offer is no longer valuable to fulfill this request. diff --git a/packages/Connectivity/framework/src/android/net/NetworkProvider.java b/packages/Connectivity/framework/src/android/net/NetworkProvider.java index d859022552c1..cfb7325c1b19 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkProvider.java +++ b/packages/Connectivity/framework/src/android/net/NetworkProvider.java @@ -168,12 +168,16 @@ public class NetworkProvider { } /** @hide */ - // TODO : make @SystemApi when the impl is complete + @SystemApi public interface NetworkOfferCallback { - /** Called by the system when a network for this offer is needed to satisfy some - * networking request. */ - void onNetworkNeeded(@NonNull NetworkRequest request, int providerId); - /** Called by the system when this offer is no longer valuable for this request. */ + /** + * Called by the system when a network for this offer is needed to satisfy some + * networking request. + */ + void onNetworkNeeded(@NonNull NetworkRequest request); + /** + * Called by the system when this offer is no longer valuable for this request. + */ void onNetworkUnneeded(@NonNull NetworkRequest request); } @@ -188,9 +192,8 @@ public class NetworkProvider { } @Override - public void onNetworkNeeded(final @NonNull NetworkRequest request, - final int providerId) { - mExecutor.execute(() -> callback.onNetworkNeeded(request, providerId)); + public void onNetworkNeeded(final @NonNull NetworkRequest request) { + mExecutor.execute(() -> callback.onNetworkNeeded(request)); } @Override @@ -254,8 +257,11 @@ public class NetworkProvider { * * The capabilities and score act as filters as to what requests the provider will see. * They are not promises, but for best performance, the providers should strive to put - * as much known information as possible in the offer. For capabilities in particular, it - * should put all NetworkAgent-managed capabilities a network may have, even if it doesn't + * as much known information as possible in the offer. For the score, it should put as + * strong a score as the networks will have, since this will filter what requests the + * provider sees – it's not a promise, it only serves to avoid sending requests that + * the provider can't ever hope to satisfy better than any current network. For capabilities, + * it should put all NetworkAgent-managed capabilities a network may have, even if it doesn't * have them at first. This applies to INTERNET, for example ; if a provider thinks the * network it can bring up for this offer may offer Internet access it should include the * INTERNET bit. It's fine if the brought up network ends up not actually having INTERNET. @@ -268,9 +274,9 @@ public class NetworkProvider { * * @hide */ - // TODO : make @SystemApi when the impl is complete + @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) - public void offerNetwork(@NonNull final NetworkScore score, + public void registerNetworkOffer(@NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps, @NonNull final Executor executor, @NonNull final NetworkOfferCallback callback) { // Can't offer a network with a provider that is not yet registered or already unregistered. @@ -307,9 +313,9 @@ public class NetworkProvider { * * @hide */ - // TODO : make @SystemApi when the impl is complete + @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) - public void unofferNetwork(final @NonNull NetworkOfferCallback callback) { + public void unregisterNetworkOffer(final @NonNull NetworkOfferCallback callback) { final NetworkOfferCallbackProxy proxy = findProxyForCallback(callback); if (null == proxy) return; mProxies.remove(proxy); diff --git a/packages/Connectivity/framework/src/android/net/NetworkScore.java b/packages/Connectivity/framework/src/android/net/NetworkScore.java index 65849930fa4a..9786b09e3508 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkScore.java +++ b/packages/Connectivity/framework/src/android/net/NetworkScore.java @@ -16,6 +16,7 @@ package android.net; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; @@ -23,6 +24,9 @@ import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Object representing the quality of a network as perceived by the user. * @@ -36,6 +40,17 @@ public final class NetworkScore implements Parcelable { // a migration. private final int mLegacyInt; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + KEEP_CONNECTED_NONE, + KEEP_CONNECTED_FOR_HANDOVER + }) + public @interface KeepConnectedReason { } + + public static final int KEEP_CONNECTED_NONE = 0; + public static final int KEEP_CONNECTED_FOR_HANDOVER = 1; + // Agent-managed policies // TODO : add them here, starting from 1 /** @hide */ @@ -46,15 +61,20 @@ public final class NetworkScore implements Parcelable { // Bitmask of all the policies applied to this score. private final long mPolicies; + private final int mKeepConnectedReason; + /** @hide */ - NetworkScore(final int legacyInt, final long policies) { + NetworkScore(final int legacyInt, final long policies, + @KeepConnectedReason final int keepConnectedReason) { mLegacyInt = legacyInt; mPolicies = policies; + mKeepConnectedReason = keepConnectedReason; } private NetworkScore(@NonNull final Parcel in) { mLegacyInt = in.readInt(); mPolicies = in.readLong(); + mKeepConnectedReason = in.readInt(); } public int getLegacyInt() { @@ -62,6 +82,13 @@ public final class NetworkScore implements Parcelable { } /** + * Returns the keep-connected reason, or KEEP_CONNECTED_NONE. + */ + public int getKeepConnectedReason() { + return mKeepConnectedReason; + } + + /** * @return whether this score has a particular policy. * * @hide @@ -80,6 +107,7 @@ public final class NetworkScore implements Parcelable { public void writeToParcel(@NonNull final Parcel dest, final int flags) { dest.writeInt(mLegacyInt); dest.writeLong(mPolicies); + dest.writeInt(mKeepConnectedReason); } @Override @@ -108,6 +136,7 @@ public final class NetworkScore implements Parcelable { private static final long POLICY_NONE = 0L; private static final int INVALID_LEGACY_INT = Integer.MIN_VALUE; private int mLegacyInt = INVALID_LEGACY_INT; + private int mKeepConnectedReason = KEEP_CONNECTED_NONE; /** * Sets the legacy int for this score. @@ -124,12 +153,23 @@ public final class NetworkScore implements Parcelable { } /** + * Set the keep-connected reason. + * + * This can be reset by calling it again with {@link KEEP_CONNECTED_NONE}. + */ + @NonNull + public Builder setKeepConnectedReason(@KeepConnectedReason final int reason) { + mKeepConnectedReason = reason; + return this; + } + + /** * Builds this NetworkScore. * @return The built NetworkScore object. */ @NonNull public NetworkScore build() { - return new NetworkScore(mLegacyInt, POLICY_NONE); + return new NetworkScore(mLegacyInt, POLICY_NONE, mKeepConnectedReason); } } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 8bb9ce940cc3..9be5f18c34a3 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3259,8 +3259,6 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.lastValidated = valid; nai.everValidated |= valid; updateCapabilities(oldScore, nai, nai.networkCapabilities); - // If score has changed, rebroadcast to NetworkProviders. b/17726566 - if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai); if (valid) { handleFreshlyValidatedNetwork(nai); // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and @@ -3681,9 +3679,12 @@ public class ConnectivityService extends IConnectivityManager.Stub if (currentNetwork != null && currentNetwork.network.getNetId() == nai.network.getNetId()) { // uid rules for this network will be removed in destroyNativeNetwork(nai). + // TODO : setting the satisfier is in fact the job of the rematch. Teach the + // rematch not to keep disconnected agents instead of setting it here ; this + // will also allow removing updating the offers below. nri.setSatisfier(null, null); - if (request.isRequest()) { - sendUpdatedScoreToFactories(request, null); + for (final NetworkOfferInfo noi : mNetworkOffers) { + informOffer(nri, noi.offer, mNetworkRanker); } if (mDefaultRequest == nri) { @@ -3811,16 +3812,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } rematchAllNetworksAndRequests(); - for (final NetworkRequestInfo nri : nris) { - // If the nri is satisfied, return as its score has already been sent if needed. - if (nri.isBeingSatisfied()) { - return; - } - // As this request was not satisfied on rematch and thus never had any scores sent to - // the factories, send null now for each request of type REQUEST. - for (final NetworkRequest req : nri.mRequests) { - if (req.isRequest()) sendUpdatedScoreToFactories(req, null); + // Requests that have not been matched to a network will not have been sent to the + // providers, because the old satisfier and the new satisfier are the same (null in this + // case). Send these requests to the providers. + for (final NetworkRequestInfo nri : nris) { + for (final NetworkOfferInfo noi : mNetworkOffers) { + informOffer(nri, noi.offer, mNetworkRanker); } } } @@ -3848,6 +3846,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // then it should be lingered. private boolean unneeded(NetworkAgentInfo nai, UnneededFor reason) { ensureRunningOnConnectivityServiceThread(); + + if (!nai.everConnected || nai.isVPN() || nai.isInactive() + || nai.getScore().getKeepConnectedReason() != NetworkScore.KEEP_CONNECTED_NONE) { + return false; + } + final int numRequests; switch (reason) { case TEARDOWN: @@ -3861,9 +3865,8 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } - if (!nai.everConnected || nai.isVPN() || nai.isInactive() || numRequests > 0) { - return false; - } + if (numRequests > 0) return false; + for (NetworkRequestInfo nri : mNetworkRequests.values()) { if (reason == UnneededFor.LINGER && !nri.isMultilayerRequest() @@ -4028,7 +4031,15 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - cancelNpiRequests(nri); + // For all outstanding offers, cancel any of the layers of this NRI that used to be + // needed for this offer. + for (final NetworkOfferInfo noi : mNetworkOffers) { + for (final NetworkRequest req : nri.mRequests) { + if (req.isRequest() && noi.offer.neededFor(req)) { + noi.offer.onNetworkUnneeded(req); + } + } + } } private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) { @@ -4041,20 +4052,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void cancelNpiRequests(@NonNull final NetworkRequestInfo nri) { - for (final NetworkRequest req : nri.mRequests) { - cancelNpiRequest(req); - } - } - - private void cancelNpiRequest(@NonNull final NetworkRequest req) { - if (req.isRequest()) { - for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) { - npi.cancelRequest(req); - } - } - } - private void removeListenRequestFromNetworks(@NonNull final NetworkRequest req) { // listens don't have a singular affected Network. Check all networks to see // if this listen request applies and remove it. @@ -4175,7 +4172,6 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.networkAgentConfig.acceptPartialConnectivity = accept; nai.updateScoreForNetworkAgentConfigUpdate(); rematchAllNetworksAndRequests(); - sendUpdatedScoreToFactories(nai); } if (always) { @@ -4243,7 +4239,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!nai.avoidUnvalidated) { nai.avoidUnvalidated = true; rematchAllNetworksAndRequests(); - sendUpdatedScoreToFactories(nai); } } @@ -4348,14 +4343,9 @@ public class ConnectivityService extends IConnectivityManager.Stub return avoidBadWifi(); } - + // TODO : this function is now useless. private void rematchForAvoidBadWifiUpdate() { rematchAllNetworksAndRequests(); - for (NetworkAgentInfo nai: mNetworkAgentInfos) { - if (nai.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - sendUpdatedScoreToFactories(nai); - } - } } // TODO: Evaluate whether this is of interest to other consumers of @@ -5349,24 +5339,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - void sendMessageToNetworkProvider(int what, int arg1, int arg2, Object obj) { - try { - messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj)); - } catch (RemoteException e) { - // Remote process died. Ignore; the death recipient will remove this - // NetworkProviderInfo from mNetworkProviderInfos. - } - } - - void requestNetwork(NetworkRequest request, int score, int servingProviderId) { - sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score, - servingProviderId, request); - } - - void cancelRequest(NetworkRequest request) { - sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request); - } - void connect(Context context, Handler handler) { try { messenger.getBinder().linkToDeath(mDeathRecipient, 0); @@ -6055,7 +6027,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) log("Got NetworkProvider Messenger for " + npi.name); mNetworkProviderInfos.put(npi.messenger, npi); npi.connect(mContext, mTrackerHandler); - sendAllRequestsToProvider(npi); } @Override @@ -6077,6 +6048,9 @@ public class ConnectivityService extends IConnectivityManager.Stub public void offerNetwork(final int providerId, @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps, @NonNull final INetworkOfferCallback callback) { + Objects.requireNonNull(score); + Objects.requireNonNull(caps); + Objects.requireNonNull(callback); final NetworkOffer offer = new NetworkOffer( FullScore.makeProspectiveScore(score, caps), caps, callback, providerId); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_OFFER, offer)); @@ -6101,7 +6075,7 @@ public class ConnectivityService extends IConnectivityManager.Stub toRemove.add(noi); } } - for (NetworkOfferInfo noi : toRemove) { + for (final NetworkOfferInfo noi : toRemove) { handleUnregisterNetworkOffer(noi); } if (DBG) log("unregisterNetworkProvider for " + npi.name); @@ -6502,7 +6476,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } mNetworkOffers.add(noi); - // TODO : send requests to the provider. + issueNetworkNeeds(noi); } private void handleUnregisterNetworkOffer(@NonNull final NetworkOfferInfo noi) { @@ -7277,100 +7251,6 @@ public class ConnectivityService extends IConnectivityManager.Stub updateLinkProperties(nai, newLp, new LinkProperties(nai.linkProperties)); } - private void sendUpdatedScoreToFactories(NetworkAgentInfo nai) { - for (int i = 0; i < nai.numNetworkRequests(); i++) { - NetworkRequest nr = nai.requestAt(i); - // Don't send listening or track default request to factories. b/17393458 - if (!nr.isRequest()) continue; - sendUpdatedScoreToFactories(nr, nai); - } - } - - private void sendUpdatedScoreToFactories( - @NonNull final NetworkReassignment.RequestReassignment event) { - // If a request of type REQUEST is now being satisfied by a new network. - if (null != event.mNewNetworkRequest && event.mNewNetworkRequest.isRequest()) { - sendUpdatedScoreToFactories(event.mNewNetworkRequest, event.mNewNetwork); - } - - // If a previously satisfied request of type REQUEST is no longer being satisfied. - if (null != event.mOldNetworkRequest && event.mOldNetworkRequest.isRequest() - && event.mOldNetworkRequest != event.mNewNetworkRequest) { - sendUpdatedScoreToFactories(event.mOldNetworkRequest, null); - } - - cancelMultilayerLowerPriorityNpiRequests(event.mNetworkRequestInfo); - } - - /** - * Cancel with all NPIs the given NRI's multilayer requests that are a lower priority than - * its currently satisfied active request. - * @param nri the NRI to cancel lower priority requests for. - */ - private void cancelMultilayerLowerPriorityNpiRequests( - @NonNull final NetworkRequestInfo nri) { - if (!nri.isMultilayerRequest() || null == nri.mActiveRequest) { - return; - } - - final int indexOfNewRequest = nri.mRequests.indexOf(nri.mActiveRequest); - for (int i = indexOfNewRequest + 1; i < nri.mRequests.size(); i++) { - cancelNpiRequest(nri.mRequests.get(i)); - } - } - - private void sendUpdatedScoreToFactories(@NonNull NetworkRequest networkRequest, - @Nullable NetworkAgentInfo nai) { - final int score; - final int serial; - if (nai != null) { - score = nai.getCurrentScore(); - serial = nai.factorySerialNumber; - } else { - score = 0; - serial = 0; - } - if (VDBG || DDBG){ - log("sending new Min Network Score(" + score + "): " + networkRequest.toString()); - } - for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) { - npi.requestNetwork(networkRequest, score, serial); - } - } - - /** Sends all current NetworkRequests to the specified factory. */ - private void sendAllRequestsToProvider(@NonNull final NetworkProviderInfo npi) { - ensureRunningOnConnectivityServiceThread(); - for (final NetworkRequestInfo nri : getNrisFromGlobalRequests()) { - for (final NetworkRequest req : nri.mRequests) { - if (!req.isRequest() && nri.getActiveRequest() == req) { - break; - } - if (!req.isRequest()) { - continue; - } - // Only set the nai for the request it is satisfying. - final NetworkAgentInfo nai = - nri.getActiveRequest() == req ? nri.getSatisfier() : null; - final int score; - final int serial; - if (null != nai) { - score = nai.getCurrentScore(); - serial = nai.factorySerialNumber; - } else { - score = 0; - serial = NetworkProvider.ID_NONE; - } - npi.requestNetwork(req, score, serial); - // For multilayer requests, don't send lower priority requests if a higher priority - // request is already satisfied. - if (null != nai) { - break; - } - } - } - } - private void sendPendingIntentForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent, int notificationType) { if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE && !nri.mPendingIntentSent) { @@ -7862,6 +7742,7 @@ public class ConnectivityService extends IConnectivityManager.Stub log(changes.toString()); // Shorter form, only one line of log } applyNetworkReassignment(changes, now); + issueNetworkNeeds(); } private void applyNetworkReassignment(@NonNull final NetworkReassignment changes, @@ -7893,12 +7774,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // before LegacyTypeTracker sends legacy broadcasts for (final NetworkReassignment.RequestReassignment event : changes.getRequestReassignments()) { - // Tell NetworkProviders about the new score, so they can stop - // trying to connect if they know they cannot match it. - // TODO - this could get expensive if there are a lot of outstanding requests for this - // network. Think of a way to reduce this. Push netid->request mapping to each factory? - sendUpdatedScoreToFactories(event); - if (null != event.mNewNetwork) { notifyNetworkAvailable(event.mNewNetwork, event.mNetworkRequestInfo); } else { @@ -8035,6 +7910,107 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private void issueNetworkNeeds() { + ensureRunningOnConnectivityServiceThread(); + for (final NetworkOfferInfo noi : mNetworkOffers) { + issueNetworkNeeds(noi); + } + } + + private void issueNetworkNeeds(@NonNull final NetworkOfferInfo noi) { + ensureRunningOnConnectivityServiceThread(); + for (final NetworkRequestInfo nri : mNetworkRequests.values()) { + informOffer(nri, noi.offer, mNetworkRanker); + } + } + + /** + * Inform a NetworkOffer about any new situation of a request. + * + * This function handles updates to offers. A number of events may happen that require + * updating the registrant for this offer about the situation : + * • The offer itself was updated. This may lead the offer to no longer being able + * to satisfy a request or beat a satisfier (and therefore be no longer needed), + * or conversely being strengthened enough to beat the satisfier (and therefore + * start being needed) + * • The network satisfying a request changed (including cases where the request + * starts or stops being satisfied). The new network may be a stronger or weaker + * match than the old one, possibly affecting whether the offer is needed. + * • The network satisfying a request updated their score. This may lead the offer + * to no longer be able to beat it if the current satisfier got better, or + * conversely start being a good choice if the current satisfier got weaker. + * + * @param nri The request + * @param offer The offer. This may be an updated offer. + */ + private static void informOffer(@NonNull NetworkRequestInfo nri, + @NonNull final NetworkOffer offer, @NonNull final NetworkRanker networkRanker) { + final NetworkRequest activeRequest = nri.isBeingSatisfied() ? nri.getActiveRequest() : null; + final NetworkAgentInfo satisfier = null != activeRequest ? nri.getSatisfier() : null; + final FullScore satisfierScore = null != satisfier ? satisfier.getScore() : null; + + // Multi-layer requests have a currently active request, the one being satisfied. + // Since the system will try to bring up a better network than is currently satisfying + // the request, NetworkProviders need to be told the offers matching the requests *above* + // the currently satisfied one are needed, that the ones *below* the satisfied one are + // not needed, and the offer is needed for the active request iff the offer can beat + // the satisfier. + // For non-multilayer requests, the logic above gracefully degenerates to only the + // last case. + // To achieve this, the loop below will proceed in three steps. In a first phase, inform + // providers that the offer is needed for this request, until the active request is found. + // In a second phase, deal with the currently active request. In a third phase, inform + // the providers that offer is unneeded for the remaining requests. + + // First phase : inform providers of all requests above the active request. + int i; + for (i = 0; nri.mRequests.size() > i; ++i) { + final NetworkRequest request = nri.mRequests.get(i); + if (activeRequest == request) break; // Found the active request : go to phase 2 + if (!request.isRequest()) continue; // Listens/track defaults are never sent to offers + // Since this request is higher-priority than the one currently satisfied, if the + // offer can satisfy it, the provider should try and bring up the network for sure ; + // no need to even ask the ranker – an offer that can satisfy is always better than + // no network. Hence tell the provider so unless it already knew. + if (request.canBeSatisfiedBy(offer.caps) && !offer.neededFor(request)) { + offer.onNetworkNeeded(request); + } + } + + // Second phase : deal with the active request (if any) + if (null != activeRequest && activeRequest.isRequest()) { + final boolean oldNeeded = offer.neededFor(activeRequest); + // An offer is needed if it is currently served by this provider or if this offer + // can beat the current satisfier. + final boolean currentlyServing = satisfier != null + && satisfier.factorySerialNumber == offer.providerId; + final boolean newNeeded = (currentlyServing + || (activeRequest.canBeSatisfiedBy(offer.caps) + && networkRanker.mightBeat(activeRequest, satisfierScore, offer))); + if (newNeeded != oldNeeded) { + if (newNeeded) { + offer.onNetworkNeeded(activeRequest); + } else { + // The offer used to be able to beat the satisfier. Now it can't. + offer.onNetworkUnneeded(activeRequest); + } + } + } + + // Third phase : inform the providers that the offer isn't needed for any request + // below the active one. + for (++i /* skip the active request */; nri.mRequests.size() > i; ++i) { + final NetworkRequest request = nri.mRequests.get(i); + if (!request.isRequest()) continue; // Listens/track defaults are never sent to offers + // Since this request is lower-priority than the one currently satisfied, if the + // offer can satisfy it, the provider should not try and bring up the network. + // Hence tell the provider so unless it already knew. + if (offer.neededFor(request)) { + offer.onNetworkUnneeded(request); + } + } + } + private void addNetworkToLegacyTypeTracker(@NonNull final NetworkAgentInfo nai) { for (int i = 0; i < nai.numNetworkRequests(); i++) { NetworkRequest nr = nai.requestAt(i); @@ -8200,7 +8176,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (VDBG || DDBG) log("updateNetworkScore for " + nai.toShortString() + " to " + score); nai.setScore(score); rematchAllNetworksAndRequests(); - sendUpdatedScoreToFactories(nai); } // Notify only this one new request of the current state. Transfer all the diff --git a/services/core/java/com/android/server/connectivity/FullScore.java b/services/core/java/com/android/server/connectivity/FullScore.java index 9326d692f6e4..375d005a0bcd 100644 --- a/services/core/java/com/android/server/connectivity/FullScore.java +++ b/services/core/java/com/android/server/connectivity/FullScore.java @@ -19,12 +19,14 @@ package com.android.server.connectivity; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkScore.KEEP_CONNECTED_NONE; import android.annotation.IntDef; import android.annotation.NonNull; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkScore; +import android.net.NetworkScore.KeepConnectedReason; import com.android.internal.annotations.VisibleForTesting; @@ -95,9 +97,13 @@ public class FullScore { // Bitmask of all the policies applied to this score. private final long mPolicies; - FullScore(final int legacyInt, final long policies) { + private final int mKeepConnectedReason; + + FullScore(final int legacyInt, final long policies, + @KeepConnectedReason final int keepConnectedReason) { mLegacyInt = legacyInt; mPolicies = policies; + mKeepConnectedReason = keepConnectedReason; } /** @@ -110,14 +116,15 @@ public class FullScore { */ public static FullScore fromNetworkScore(@NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config) { - return withPolicies(score.getLegacyInt(), caps.hasCapability(NET_CAPABILITY_VALIDATED), + return withPolicies(score.getLegacyInt(), score.getKeepConnectedReason(), + caps.hasCapability(NET_CAPABILITY_VALIDATED), caps.hasTransport(TRANSPORT_VPN), config.explicitlySelected, config.acceptUnvalidated); } /** - * Given a score supplied by the NetworkAgent, produce a prospective score for an offer. + * Given a score supplied by a NetworkProvider, produce a prospective score for an offer. * * NetworkOffers have score filters that are compared to the scores of actual networks * to see if they could possibly beat the current satisfier. Some things the agent can't @@ -139,8 +146,8 @@ public class FullScore { final boolean everUserSelected = false; // Don't assume the user will accept unvalidated connectivity. final boolean acceptUnvalidated = false; - return withPolicies(score.getLegacyInt(), mayValidate, vpn, everUserSelected, - acceptUnvalidated); + return withPolicies(score.getLegacyInt(), KEEP_CONNECTED_NONE, + mayValidate, vpn, everUserSelected, acceptUnvalidated); } /** @@ -152,13 +159,15 @@ public class FullScore { */ public FullScore mixInScore(@NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config) { - return withPolicies(mLegacyInt, caps.hasCapability(NET_CAPABILITY_VALIDATED), + return withPolicies(mLegacyInt, mKeepConnectedReason, + caps.hasCapability(NET_CAPABILITY_VALIDATED), caps.hasTransport(TRANSPORT_VPN), config.explicitlySelected, config.acceptUnvalidated); } private static FullScore withPolicies(@NonNull final int legacyInt, + @KeepConnectedReason final int keepConnectedReason, final boolean isValidated, final boolean isVpn, final boolean everUserSelected, @@ -167,7 +176,8 @@ public class FullScore { (isValidated ? 1L << POLICY_IS_VALIDATED : 0) | (isVpn ? 1L << POLICY_IS_VPN : 0) | (everUserSelected ? 1L << POLICY_EVER_USER_SELECTED : 0) - | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0)); + | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0), + keepConnectedReason); } /** @@ -219,13 +229,21 @@ public class FullScore { return 0 != (mPolicies & (1L << policy)); } + /** + * Returns the keep-connected reason, or KEEP_CONNECTED_NONE. + */ + public int getKeepConnectedReason() { + return mKeepConnectedReason; + } + // Example output : // Score(50 ; Policies : EVER_USER_SELECTED&IS_VALIDATED) @Override public String toString() { final StringJoiner sj = new StringJoiner( "&", // delimiter - "Score(" + mLegacyInt + " ; Policies : ", // prefix + "Score(" + mLegacyInt + " ; KeepConnected : " + mKeepConnectedReason + + " ; Policies : ", // prefix ")"); // suffix for (int i = NetworkScore.MIN_AGENT_MANAGED_POLICY; i <= NetworkScore.MAX_AGENT_MANAGED_POLICY; ++i) { diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 97df5bff4946..b31fa7085147 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -884,6 +884,10 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { return isWifi && !avoidBadWifi && everValidated; } + public FullScore getScore() { + return mScore; + } + // Get the current score for this Network. This may be modified from what the // NetworkAgent sent, as it has modifiers applied to it. public int getCurrentScore() { diff --git a/services/core/java/com/android/server/connectivity/NetworkOffer.java b/services/core/java/com/android/server/connectivity/NetworkOffer.java index 548db6b6ddbb..a0d392445472 100644 --- a/services/core/java/com/android/server/connectivity/NetworkOffer.java +++ b/services/core/java/com/android/server/connectivity/NetworkOffer.java @@ -17,13 +17,14 @@ package com.android.server.connectivity; import android.annotation.NonNull; -import android.annotation.Nullable; import android.net.INetworkOfferCallback; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.os.RemoteException; +import java.util.HashSet; import java.util.Objects; - +import java.util.Set; /** * Represents an offer made by a NetworkProvider to create a network if a need arises. @@ -44,46 +45,86 @@ public class NetworkOffer { @NonNull public final NetworkCapabilities caps; @NonNull public final INetworkOfferCallback callback; @NonNull public final int providerId; + // While this could, in principle, be deduced from the old values of the satisfying networks, + // doing so would add a lot of complexity and performance penalties. For each request, the + // ranker would have to run again to figure out if this offer used to be able to beat the + // previous satisfier to know if there is a change in whether this offer is now needed ; + // besides, there would be a need to handle an edge case when a new request comes online, + // where it's not satisfied before the first rematch, where starting to satisfy a request + // should not result in sending unneeded to this offer. This boolean, while requiring that + // the offers are only ever manipulated on the CS thread, is by far a simpler and + // economical solution. + private final Set<NetworkRequest> mCurrentlyNeeded = new HashSet<>(); - private static NetworkCapabilities emptyCaps() { - final NetworkCapabilities nc = new NetworkCapabilities(); - return nc; - } - - // Ideally the caps argument would be non-null, but null has historically meant no filter - // and telephony passes null. Keep backward compatibility. public NetworkOffer(@NonNull final FullScore score, - @Nullable final NetworkCapabilities caps, + @NonNull final NetworkCapabilities caps, @NonNull final INetworkOfferCallback callback, @NonNull final int providerId) { this.score = Objects.requireNonNull(score); - this.caps = null != caps ? caps : emptyCaps(); + this.caps = Objects.requireNonNull(caps); this.callback = Objects.requireNonNull(callback); this.providerId = providerId; } /** + * Tell the provider for this offer that the network is needed for a request. + * @param request the request for which the offer is needed + */ + public void onNetworkNeeded(@NonNull final NetworkRequest request) { + if (mCurrentlyNeeded.contains(request)) { + throw new IllegalStateException("Network already needed"); + } + mCurrentlyNeeded.add(request); + try { + callback.onNetworkNeeded(request); + } catch (final RemoteException e) { + // The provider is dead. It will be removed by the death recipient. + } + } + + /** + * Tell the provider for this offer that the network is no longer needed for this request. + * + * onNetworkNeeded will have been called with the same request before. + * + * @param request the request + */ + public void onNetworkUnneeded(@NonNull final NetworkRequest request) { + if (!mCurrentlyNeeded.contains(request)) { + throw new IllegalStateException("Network already unneeded"); + } + mCurrentlyNeeded.remove(request); + try { + callback.onNetworkUnneeded(request); + } catch (final RemoteException e) { + // The provider is dead. It will be removed by the death recipient. + } + } + + /** + * Returns whether this offer is currently needed for this request. + * @param request the request + * @return whether the offer is currently considered needed + */ + public boolean neededFor(@NonNull final NetworkRequest request) { + return mCurrentlyNeeded.contains(request); + } + + /** * Migrate from, and take over, a previous offer. * * When an updated offer is sent from a provider, call this method on the new offer, passing * the old one, to take over the state. * - * @param previousOffer + * @param previousOffer the previous offer */ public void migrateFrom(@NonNull final NetworkOffer previousOffer) { if (!callback.equals(previousOffer.callback)) { throw new IllegalArgumentException("Can only migrate from a previous version of" + " the same offer"); } - } - - /** - * Returns whether an offer can satisfy a NetworkRequest, according to its capabilities. - * @param request The request to test against. - * @return Whether this offer can satisfy the request. - */ - public final boolean canSatisfy(@NonNull final NetworkRequest request) { - return request.networkCapabilities.satisfiedByNetworkCapabilities(caps); + mCurrentlyNeeded.clear(); + mCurrentlyNeeded.addAll(previousOffer.mCurrentlyNeeded); } @Override diff --git a/services/core/java/com/android/server/connectivity/NetworkRanker.java b/services/core/java/com/android/server/connectivity/NetworkRanker.java index d0aabf95d572..0e4dda22886e 100644 --- a/services/core/java/com/android/server/connectivity/NetworkRanker.java +++ b/services/core/java/com/android/server/connectivity/NetworkRanker.java @@ -18,6 +18,7 @@ package com.android.server.connectivity; import android.annotation.NonNull; import android.annotation.Nullable; +import android.net.NetworkCapabilities; import android.net.NetworkRequest; import java.util.Collection; @@ -47,4 +48,41 @@ public class NetworkRanker { } return bestNetwork; } + + /** + * Returns whether an offer has a chance to beat a champion network for a request. + * + * Offers are sent by network providers when they think they might be able to make a network + * with the characteristics contained in the offer. If the offer has no chance to beat + * the currently best network for a given request, there is no point in the provider spending + * power trying to find and bring up such a network. + * + * Note that having an offer up does not constitute a commitment from the provider part + * to be able to bring up a network with these characteristics, or a network at all for + * that matter. This is only used to save power by letting providers know when they can't + * beat a current champion. + * + * @param request The request to evaluate against. + * @param championScore The currently best network for this request. + * @param offer The offer. + * @return Whether the offer stands a chance to beat the champion. + */ + public boolean mightBeat(@NonNull final NetworkRequest request, + @Nullable final FullScore championScore, + @NonNull final NetworkOffer offer) { + // If this network can't even satisfy the request then it can't beat anything, not + // even an absence of network. It can't satisfy it anyway. + if (!request.canBeSatisfiedBy(offer.caps)) return false; + // If there is no satisfying network, then this network can beat, because some network + // is always better than no network. + if (null == championScore) return true; + final int offerIntScore; + if (offer.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + // If the offer might have Internet access, then it might validate. + offerIntScore = offer.score.getLegacyIntAsValidated(); + } else { + offerIntScore = offer.score.getLegacyInt(); + } + return championScore.getLegacyInt() < offerIntScore; + } } diff --git a/tests/net/common/java/android/net/NetworkProviderTest.kt b/tests/net/common/java/android/net/NetworkProviderTest.kt index 340e6f963137..7424157bea74 100644 --- a/tests/net/common/java/android/net/NetworkProviderTest.kt +++ b/tests/net/common/java/android/net/NetworkProviderTest.kt @@ -198,4 +198,4 @@ class NetworkProviderTest { cb.expectCallback<OnUnavailable>() { nr.getNetworkSpecifier() == specifier } mCm.unregisterNetworkProvider(provider) } -}
\ No newline at end of file +} diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index e2d43cbb8efd..6245e8542b04 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -41,6 +41,7 @@ import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkProvider; +import android.net.NetworkScore; import android.net.NetworkSpecifier; import android.net.QosFilter; import android.net.SocketKeepalive; @@ -199,6 +200,11 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { } } + public void setScore(@NonNull final NetworkScore score) { + mScore = score.getLegacyInt(); + mNetworkAgent.sendNetworkScore(score); + } + public void adjustScore(int change) { mScore += change; mNetworkAgent.sendNetworkScore(mScore); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 44298d4b7652..c7e6a056628d 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -94,6 +94,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; +import static android.net.NetworkScore.KEEP_CONNECTED_FOR_HANDOVER; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; @@ -977,8 +978,6 @@ public class ConnectivityServiceTest { * operations have been processed and test for them. */ private static class MockNetworkFactory extends NetworkFactory { - private final ConditionVariable mNetworkStartedCV = new ConditionVariable(); - private final ConditionVariable mNetworkStoppedCV = new ConditionVariable(); private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false); static class RequestEntry { @@ -990,11 +989,8 @@ public class ConnectivityServiceTest { } static final class Add extends RequestEntry { - public final int factorySerialNumber; - - Add(@NonNull final NetworkRequest request, final int factorySerialNumber) { + Add(@NonNull final NetworkRequest request) { super(request); - this.factorySerialNumber = factorySerialNumber; } } @@ -1003,6 +999,11 @@ public class ConnectivityServiceTest { super(request); } } + + @Override + public String toString() { + return "RequestEntry [ " + getClass().getName() + " : " + request + " ]"; + } } // History of received requests adds and removes. @@ -1014,7 +1015,6 @@ public class ConnectivityServiceTest { return obj; } - public RequestEntry.Add expectRequestAdd() { return failIfNull((RequestEntry.Add) mRequestHistory.poll(TIMEOUT_MS, it -> it instanceof RequestEntry.Add), "Expected request add"); @@ -1054,40 +1054,28 @@ public class ConnectivityServiceTest { protected void startNetwork() { mNetworkStarted.set(true); - mNetworkStartedCV.open(); } protected void stopNetwork() { mNetworkStarted.set(false); - mNetworkStoppedCV.open(); } public boolean getMyStartRequested() { return mNetworkStarted.get(); } - public ConditionVariable getNetworkStartedCV() { - mNetworkStartedCV.close(); - return mNetworkStartedCV; - } - - public ConditionVariable getNetworkStoppedCV() { - mNetworkStoppedCV.close(); - return mNetworkStoppedCV; - } @Override - protected void handleAddRequest(NetworkRequest request, int score, - int factorySerialNumber) { + protected void needNetworkFor(NetworkRequest request) { mNetworkRequests.put(request.requestId, request); - super.handleAddRequest(request, score, factorySerialNumber); - mRequestHistory.add(new RequestEntry.Add(request, factorySerialNumber)); + super.needNetworkFor(request); + mRequestHistory.add(new RequestEntry.Add(request)); } @Override - protected void handleRemoveRequest(NetworkRequest request) { + protected void releaseNetworkFor(NetworkRequest request) { mNetworkRequests.remove(request.requestId); - super.handleRemoveRequest(request); + super.releaseNetworkFor(request); mRequestHistory.add(new RequestEntry.Remove(request)); } @@ -2919,25 +2907,23 @@ public class ConnectivityServiceTest { handlerThread.start(); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), mServiceContext, "testFactory", filter, mCsHandlerThread); - testFactory.setScoreFilter(40); - ConditionVariable cv = testFactory.getNetworkStartedCV(); + testFactory.setScoreFilter(45); testFactory.register(); - testFactory.expectRequestAdd(); - testFactory.assertRequestCountEquals(1); - int expectedRequestCount = 1; - NetworkCallback networkCallback = null; - // For non-INTERNET capabilities we cannot rely on the default request being present, so - // add one. + + final NetworkCallback networkCallback; if (capability != NET_CAPABILITY_INTERNET) { + // If the capability passed in argument is part of the default request, then the + // factory will see the default request. Otherwise the filter will prevent the + // factory from seeing it. In that case, add a request so it can be tested. assertFalse(testFactory.getMyStartRequested()); NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build(); networkCallback = new NetworkCallback(); mCm.requestNetwork(request, networkCallback); - expectedRequestCount++; - testFactory.expectRequestAdd(); + } else { + networkCallback = null; } - waitFor(cv); - testFactory.assertRequestCountEquals(expectedRequestCount); + testFactory.expectRequestAdd(); + testFactory.assertRequestCountEquals(1); assertTrue(testFactory.getMyStartRequested()); // Now bring in a higher scored network. @@ -2946,20 +2932,45 @@ public class ConnectivityServiceTest { // own NetworkRequest during startup, just bump up the score to cancel out the // unvalidated penalty. testAgent.adjustScore(40); - cv = testFactory.getNetworkStoppedCV(); - // When testAgent connects, ConnectivityService will re-send us all current requests with - // the new score. There are expectedRequestCount such requests, and we must wait for all of - // them. + // When testAgent connects, because of its 50 score (50 for cell + 40 adjustment score + // - 40 penalty for not being validated), it will beat the testFactory's offer, so + // the request will be removed. testAgent.connect(false); testAgent.addCapability(capability); - waitFor(cv); - testFactory.expectRequestAdds(expectedRequestCount); - testFactory.assertRequestCountEquals(expectedRequestCount); + testFactory.expectRequestRemove(); + testFactory.assertRequestCountEquals(0); + assertFalse(testFactory.getMyStartRequested()); + + // Add a request and make sure it's not sent to the factory, because the agent + // is satisfying it better. + final NetworkCallback cb = new ConnectivityManager.NetworkCallback(); + mCm.requestNetwork(new NetworkRequest.Builder().addCapability(capability).build(), cb); + expectNoRequestChanged(testFactory); + testFactory.assertRequestCountEquals(0); assertFalse(testFactory.getMyStartRequested()); + // Make the test agent weak enough to have the exact same score as the + // factory (50 for cell + 40 adjustment -40 validation penalty - 5 adjustment). Make sure + // the factory doesn't see the request. + testAgent.adjustScore(-5); + expectNoRequestChanged(testFactory); + assertFalse(testFactory.getMyStartRequested()); + + // Make the test agent weak enough to see the two requests (the one that was just sent, + // and either the default one or the one sent at the top of this test if the default + // won't be seen). + testAgent.adjustScore(-45); + testFactory.expectRequestAdds(2); + testFactory.assertRequestCountEquals(2); + assertTrue(testFactory.getMyStartRequested()); + + // Now unregister and make sure the request is removed. + mCm.unregisterNetworkCallback(cb); + testFactory.expectRequestRemove(); + // Bring in a bunch of requests. - assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); + assertEquals(1, testFactory.getMyRequestCount()); ConnectivityManager.NetworkCallback[] networkCallbacks = new ConnectivityManager.NetworkCallback[10]; for (int i = 0; i< networkCallbacks.length; i++) { @@ -2969,24 +2980,28 @@ public class ConnectivityServiceTest { mCm.requestNetwork(builder.build(), networkCallbacks[i]); } testFactory.expectRequestAdds(10); - testFactory.assertRequestCountEquals(10 + expectedRequestCount); - assertFalse(testFactory.getMyStartRequested()); + testFactory.assertRequestCountEquals(11); // +1 for the default/test specific request + assertTrue(testFactory.getMyStartRequested()); // Remove the requests. for (int i = 0; i < networkCallbacks.length; i++) { mCm.unregisterNetworkCallback(networkCallbacks[i]); } testFactory.expectRequestRemoves(10); - testFactory.assertRequestCountEquals(expectedRequestCount); + testFactory.assertRequestCountEquals(1); + assertTrue(testFactory.getMyStartRequested()); + + // Adjust the agent score up again. Expect the request to be withdrawn. + testAgent.adjustScore(50); + testFactory.expectRequestRemove(); + testFactory.assertRequestCountEquals(0); assertFalse(testFactory.getMyStartRequested()); // Drop the higher scored network. - cv = testFactory.getNetworkStartedCV(); testAgent.disconnect(); - waitFor(cv); - testFactory.expectRequestAdds(expectedRequestCount); - testFactory.assertRequestCountEquals(expectedRequestCount); - assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); + testFactory.expectRequestAdd(); + testFactory.assertRequestCountEquals(1); + assertEquals(1, testFactory.getMyRequestCount()); assertTrue(testFactory.getMyStartRequested()); testFactory.terminate(); @@ -3016,9 +3031,34 @@ public class ConnectivityServiceTest { } @Test + public void testRegisterIgnoringScore() throws Exception { + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.adjustScore(30); // score = 60 (wifi) + 30 = 90 + mWiFiNetworkAgent.connect(true /* validated */); + + // Make sure the factory sees the default network + final NetworkCapabilities filter = new NetworkCapabilities(); + filter.addCapability(NET_CAPABILITY_INTERNET); + filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); + final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); + handlerThread.start(); + final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "testFactory", filter, mCsHandlerThread); + testFactory.registerIgnoringScore(); + testFactory.expectRequestAdd(); + + mWiFiNetworkAgent.adjustScore(20); // exceed the maximum score + expectNoRequestChanged(testFactory); // still seeing the request + + mWiFiNetworkAgent.disconnect(); + } + + @Test public void testNetworkFactoryUnregister() throws Exception { + // Make sure the factory sees the default network final NetworkCapabilities filter = new NetworkCapabilities(); - filter.clearAll(); + filter.addCapability(NET_CAPABILITY_INTERNET); + filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); handlerThread.start(); @@ -4282,6 +4322,7 @@ public class ConnectivityServiceTest { testFactory.register(); try { + // Expect the factory to receive the default network request. testFactory.expectRequestAdd(); testFactory.assertRequestCountEquals(1); assertTrue(testFactory.getMyStartRequested()); @@ -4290,25 +4331,44 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); // Score 60 - 40 penalty for not validated yet, then 60 when it validates mWiFiNetworkAgent.connect(true); - // Default request and mobile always on request - testFactory.expectRequestAdds(2); + // The network connects with a low score, so the offer can still beat it and + // nothing happens. Then the network validates, and the offer with its filter score + // of 40 can no longer beat it and the request is removed. + testFactory.expectRequestRemove(); + testFactory.assertRequestCountEquals(0); + assertFalse(testFactory.getMyStartRequested()); - // Turn on mobile data always on. The factory starts looking again. + // Turn on mobile data always on. This request will not match the wifi request, so + // it will be sent to the test factory whose filters allow to see it. setAlwaysOnNetworks(true); testFactory.expectRequestAdd(); - testFactory.assertRequestCountEquals(2); + testFactory.assertRequestCountEquals(1); assertTrue(testFactory.getMyStartRequested()); // Bring up cell data and check that the factory stops looking. assertLength(1, mCm.getAllNetworks()); mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); - testFactory.expectRequestAdds(2); // Unvalidated and validated - testFactory.assertRequestCountEquals(2); - // The cell network outscores the factory filter, so start is not requested. + mCellNetworkAgent.connect(false); + cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent, false, false, false, + TEST_CALLBACK_TIMEOUT_MS); + // When cell connects, it will satisfy the "mobile always on request" right away + // by virtue of being the only network that can satisfy the request. However, its + // score is low (50 - 40 = 10) so the test factory can still hope to beat it. + expectNoRequestChanged(testFactory); + + // Next, cell validates. This gives it a score of 50 and the test factory can't + // hope to beat that according to its filters. It will see the message that its + // offer is now unnecessary. + mCellNetworkAgent.setNetworkValid(true); + // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is + // validated – see testPartialConnectivity. + mCm.reportNetworkConnectivity(mCellNetworkAgent.getNetwork(), true); + cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent); + testFactory.expectRequestRemove(); + testFactory.assertRequestCountEquals(0); + // Accordingly, the factory shouldn't be started. assertFalse(testFactory.getMyStartRequested()); // Check that cell data stays up. @@ -4316,10 +4376,27 @@ public class ConnectivityServiceTest { verifyActiveNetwork(TRANSPORT_WIFI); assertLength(2, mCm.getAllNetworks()); - // Turn off mobile data always on and expect the request to disappear... - setAlwaysOnNetworks(false); - testFactory.expectRequestRemove(); + // Cell disconnects. There is still the "mobile data always on" request outstanding, + // and the test factory should see it now that it isn't hopelessly outscored. + mCellNetworkAgent.disconnect(); + cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + assertLength(1, mCm.getAllNetworks()); + testFactory.expectRequestAdd(); + testFactory.assertRequestCountEquals(1); + // Reconnect cell validated, see the request disappear again. Then withdraw the + // mobile always on request. This will tear down cell, and there shouldn't be a + // blip where the test factory briefly sees the request or anything. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + assertLength(2, mCm.getAllNetworks()); + testFactory.expectRequestRemove(); + testFactory.assertRequestCountEquals(0); + setAlwaysOnNetworks(false); + expectNoRequestChanged(testFactory); + testFactory.assertRequestCountEquals(0); + assertFalse(testFactory.getMyStartRequested()); // ... and cell data to be torn down after nascent network timeout. cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, mService.mNascentDelayMs + TEST_CALLBACK_TIMEOUT_MS); @@ -4622,7 +4699,8 @@ public class ConnectivityServiceTest { handlerThread.start(); NetworkCapabilities filter = new NetworkCapabilities() .addTransportType(TRANSPORT_WIFI) - .addCapability(NET_CAPABILITY_INTERNET); + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), mServiceContext, "testFactory", filter, mCsHandlerThread); testFactory.setScoreFilter(40); @@ -4631,32 +4709,43 @@ public class ConnectivityServiceTest { testFactory.register(); testFactory.expectRequestAdd(); - // Now file the test request and expect it. - mCm.requestNetwork(nr, networkCallback); - final NetworkRequest newRequest = testFactory.expectRequestAdd().request; + try { + // Now file the test request and expect it. + mCm.requestNetwork(nr, networkCallback); + final NetworkRequest newRequest = testFactory.expectRequestAdd().request; - if (preUnregister) { - mCm.unregisterNetworkCallback(networkCallback); + if (preUnregister) { + mCm.unregisterNetworkCallback(networkCallback); - // Simulate the factory releasing the request as unfulfillable: no-op since - // the callback has already been unregistered (but a test that no exceptions are - // thrown). - testFactory.triggerUnfulfillable(newRequest); - } else { - // Simulate the factory releasing the request as unfulfillable and expect onUnavailable! - testFactory.triggerUnfulfillable(newRequest); + // The request has been released : the factory should see it removed + // immediately. + testFactory.expectRequestRemove(); - networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null); + // Simulate the factory releasing the request as unfulfillable: no-op since + // the callback has already been unregistered (but a test that no exceptions are + // thrown). + testFactory.triggerUnfulfillable(newRequest); + } else { + // Simulate the factory releasing the request as unfulfillable and expect + // onUnavailable! + testFactory.triggerUnfulfillable(newRequest); - // unregister network callback - a no-op (since already freed by the - // on-unavailable), but should not fail or throw exceptions. - mCm.unregisterNetworkCallback(networkCallback); - } + networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null); - testFactory.expectRequestRemove(); + // Declaring a request unfulfillable releases it automatically. + testFactory.expectRequestRemove(); - testFactory.terminate(); - handlerThread.quit(); + // unregister network callback - a no-op (since already freed by the + // on-unavailable), but should not fail or throw exceptions. + mCm.unregisterNetworkCallback(networkCallback); + + // The factory should not see any further removal, as this request has + // already been removed. + } + } finally { + testFactory.terminate(); + handlerThread.quit(); + } } private static class TestKeepaliveCallback extends PacketKeepaliveCallback { @@ -9851,6 +9940,83 @@ public class ConnectivityServiceTest { } } + @Test + public void testKeepConnected() throws Exception { + setAlwaysOnNetworks(false); + registerDefaultNetworkCallbacks(); + final TestNetworkCallback allNetworksCb = new TestNetworkCallback(); + final NetworkRequest allNetworksRequest = new NetworkRequest.Builder().clearCapabilities() + .build(); + mCm.registerNetworkCallback(allNetworksRequest, allNetworksCb); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true /* validated */); + + mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true /* validated */); + + mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + // While the default callback doesn't see the network before it's validated, the listen + // sees the network come up and validate later + allNetworksCb.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + allNetworksCb.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); + allNetworksCb.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, + TEST_LINGER_DELAY_MS * 2); + + // The cell network has disconnected (see LOST above) because it was outscored and + // had no requests (see setAlwaysOnNetworks(false) above) + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + final NetworkScore score = new NetworkScore.Builder().setLegacyInt(30).build(); + mCellNetworkAgent.setScore(score); + mCellNetworkAgent.connect(false /* validated */); + + // The cell network gets torn down right away. + allNetworksCb.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, + TEST_NASCENT_DELAY_MS * 2); + allNetworksCb.assertNoCallback(); + + // Now create a cell network with KEEP_CONNECTED_FOR_HANDOVER and make sure it's + // not disconnected immediately when outscored. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + final NetworkScore scoreKeepup = new NetworkScore.Builder().setLegacyInt(30) + .setKeepConnectedReason(KEEP_CONNECTED_FOR_HANDOVER).build(); + mCellNetworkAgent.setScore(scoreKeepup); + mCellNetworkAgent.connect(true /* validated */); + + allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + mDefaultNetworkCallback.assertNoCallback(); + + mWiFiNetworkAgent.disconnect(); + + allNetworksCb.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + + // Reconnect a WiFi network and make sure the cell network is still not torn down. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true /* validated */); + + allNetworksCb.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + + // Now remove the reason to keep connected and make sure the network lingers and is + // torn down. + mCellNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).build()); + allNetworksCb.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent, + TEST_NASCENT_DELAY_MS * 2); + allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, + TEST_LINGER_DELAY_MS * 2); + mDefaultNetworkCallback.assertNoCallback(); + + mCm.unregisterNetworkCallback(allNetworksCb); + // mDefaultNetworkCallback will be unregistered by tearDown() + } + private class QosCallbackMockHelper { @NonNull public final QosFilter mFilter; @NonNull public final IQosCallback mCallback; @@ -11210,6 +11376,115 @@ public class ConnectivityServiceTest { // default callbacks will be unregistered in tearDown } + @Test + public void testNetworkFactoryRequestsWithMultilayerRequest() + throws Exception { + // First use OEM_PAID preference to create a multi-layer request : 1. listen for + // unmetered, 2. request network with cap OEM_PAID, 3, request the default network for + // fallback. + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + + final HandlerThread handlerThread = new HandlerThread("MockFactory"); + handlerThread.start(); + NetworkCapabilities internetFilter = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); + final MockNetworkFactory internetFactory = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "internetFactory", internetFilter, mCsHandlerThread); + internetFactory.setScoreFilter(40); + internetFactory.register(); + // Default internet request & 3rd (fallback) request in OEM_PAID NRI. The unmetered request + // is never sent to factories (it's a LISTEN, not requestable) and the OEM_PAID request + // doesn't match the internetFactory filter. + internetFactory.expectRequestAdds(2); + + NetworkCapabilities oemPaidFilter = new NetworkCapabilities() + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_OEM_PAID) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + final MockNetworkFactory oemPaidFactory = new MockNetworkFactory(handlerThread.getLooper(), + mServiceContext, "oemPaidFactory", oemPaidFilter, mCsHandlerThread); + oemPaidFactory.setScoreFilter(40); + oemPaidFactory.register(); + oemPaidFactory.expectRequestAdd(); // Because nobody satisfies the request + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + // A network connected that satisfies the default internet request. For the OEM_PAID + // preference, this is not as good as an OEM_PAID network, so even if the score of + // the network is better than the factory announced, it still should try to bring up + // the network. + expectNoRequestChanged(oemPaidFactory); + oemPaidFactory.assertRequestCountEquals(1); + // The internet factory however is outscored, and should lose its requests. + internetFactory.expectRequestRemoves(2); + internetFactory.assertRequestCountEquals(0); + + final NetworkCapabilities oemPaidNc = new NetworkCapabilities(); + oemPaidNc.addCapability(NET_CAPABILITY_OEM_PAID); + oemPaidNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + final TestNetworkAgentWrapper oemPaidAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, + new LinkProperties(), oemPaidNc); + oemPaidAgent.connect(true); + + // The oemPaidAgent has score 50 (default for cell) so it beats what the oemPaidFactory can + // provide, therefore it loses the request. + oemPaidFactory.expectRequestRemove(); + oemPaidFactory.assertRequestCountEquals(0); + expectNoRequestChanged(internetFactory); + internetFactory.assertRequestCountEquals(0); + + oemPaidAgent.adjustScore(-30); + // Now the that the agent is weak, the oemPaidFactory can beat the existing network for the + // OEM_PAID request. The internet factory however can't beat a network that has OEM_PAID + // for the preference request, so it doesn't see the request. + oemPaidFactory.expectRequestAdd(); + oemPaidFactory.assertRequestCountEquals(1); + expectNoRequestChanged(internetFactory); + internetFactory.assertRequestCountEquals(0); + + mCellNetworkAgent.disconnect(); + // The network satisfying the default internet request has disconnected, so the + // internetFactory sees the default request again. However there is a network with OEM_PAID + // connected, so the 2nd OEM_PAID req is already satisfied, so the oemPaidFactory doesn't + // care about networks that don't have OEM_PAID. + expectNoRequestChanged(oemPaidFactory); + oemPaidFactory.assertRequestCountEquals(1); + internetFactory.expectRequestAdd(); + internetFactory.assertRequestCountEquals(1); + + // Cell connects again, still with score 50. Back to the previous state. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + expectNoRequestChanged(oemPaidFactory); + oemPaidFactory.assertRequestCountEquals(1); + internetFactory.expectRequestRemove(); + internetFactory.assertRequestCountEquals(0); + + // Now WiFi connects and it's unmetered, but it's weaker than cell. + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.adjustScore(-30); // Not the best Internet network, but unmetered + mWiFiNetworkAgent.connect(true); + + // The OEM_PAID preference prefers an unmetered network to an OEM_PAID network, so + // the oemPaidFactory can't beat this no matter how high its score. + oemPaidFactory.expectRequestRemove(); + expectNoRequestChanged(internetFactory); + + mCellNetworkAgent.disconnect(); + // Now that the best internet network (cell, with its 50 score compared to 30 for WiFi + // at this point), the default internet request is satisfied by a network worse than + // the internetFactory announced, so it gets the request. However, there is still an + // unmetered network, so the oemPaidNetworkFactory still can't beat this. + expectNoRequestChanged(oemPaidFactory); + internetFactory.expectRequestAdd(); + } + /** * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order: * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID @@ -11504,11 +11779,11 @@ public class ConnectivityServiceTest { testFactory.setScoreFilter(40); try { - // Register the factory and expect it will see default request, because all requests - // are sent to all factories. + // Register the factory. It doesn't see the default request because its filter does + // not include INTERNET. testFactory.register(); - testFactory.expectRequestAdd(); - testFactory.assertRequestCountEquals(1); + expectNoRequestChanged(testFactory); + testFactory.assertRequestCountEquals(0); // The factory won't try to start the network since the default request doesn't // match the filter (no INTERNET capability). assertFalse(testFactory.getMyStartRequested()); @@ -11521,7 +11796,7 @@ public class ConnectivityServiceTest { bestMatchingCb, mCsHandlerThread.getThreadHandler()); bestMatchingCb.assertNoCallback(); expectNoRequestChanged(testFactory); - testFactory.assertRequestCountEquals(1); + testFactory.assertRequestCountEquals(0); assertFalse(testFactory.getMyStartRequested()); // Fire a normal mms request, verify the factory will only see the request. @@ -11530,13 +11805,13 @@ public class ConnectivityServiceTest { .addCapability(NET_CAPABILITY_MMS).build(); mCm.requestNetwork(mmsRequest, mmsNetworkCallback); testFactory.expectRequestAdd(); - testFactory.assertRequestCountEquals(2); + testFactory.assertRequestCountEquals(1); assertTrue(testFactory.getMyStartRequested()); // Unregister best matching callback, verify factory see no change. mCm.unregisterNetworkCallback(bestMatchingCb); expectNoRequestChanged(testFactory); - testFactory.assertRequestCountEquals(2); + testFactory.assertRequestCountEquals(1); assertTrue(testFactory.getMyStartRequested()); } finally { testFactory.terminate(); diff --git a/tests/net/java/com/android/server/connectivity/FullScoreTest.kt b/tests/net/java/com/android/server/connectivity/FullScoreTest.kt index eb3b4df1a282..2864bc7e794a 100644 --- a/tests/net/java/com/android/server/connectivity/FullScoreTest.kt +++ b/tests/net/java/com/android/server/connectivity/FullScoreTest.kt @@ -18,6 +18,7 @@ package com.android.server.connectivity import android.net.NetworkAgentConfig import android.net.NetworkCapabilities +import android.net.NetworkScore.KEEP_CONNECTED_NONE import android.text.TextUtils import android.util.ArraySet import androidx.test.filters.SmallTest @@ -60,11 +61,11 @@ class FullScoreTest { @Test fun testGetLegacyInt() { - val ns = FullScore(50, 0L /* policy */) + val ns = FullScore(50, 0L /* policy */, KEEP_CONNECTED_NONE) assertEquals(10, ns.legacyInt) // -40 penalty for not being validated assertEquals(50, ns.legacyIntAsValidated) - val vpnNs = FullScore(101, 0L /* policy */).withPolicies(vpn = true) + val vpnNs = FullScore(101, 0L /* policy */, KEEP_CONNECTED_NONE).withPolicies(vpn = true) assertEquals(101, vpnNs.legacyInt) // VPNs are not subject to unvalidation penalty assertEquals(101, vpnNs.legacyIntAsValidated) assertEquals(101, vpnNs.withPolicies(validated = true).legacyInt) @@ -83,7 +84,7 @@ class FullScoreTest { @Test fun testToString() { - val string = FullScore(10, 0L /* policy */) + val string = FullScore(10, 0L /* policy */, KEEP_CONNECTED_NONE) .withPolicies(vpn = true, acceptUnvalidated = true).toString() assertTrue(string.contains("Score(10"), string) assertTrue(string.contains("ACCEPT_UNVALIDATED"), string) @@ -107,7 +108,7 @@ class FullScoreTest { @Test fun testHasPolicy() { - val ns = FullScore(50, 0L /* policy */) + val ns = FullScore(50, 0L /* policy */, KEEP_CONNECTED_NONE) assertFalse(ns.hasPolicy(POLICY_IS_VALIDATED)) assertFalse(ns.hasPolicy(POLICY_IS_VPN)) assertFalse(ns.hasPolicy(POLICY_EVER_USER_SELECTED)) diff --git a/tests/net/java/com/android/server/connectivity/NetworkOfferTest.kt b/tests/net/java/com/android/server/connectivity/NetworkOfferTest.kt new file mode 100644 index 000000000000..409f8c309935 --- /dev/null +++ b/tests/net/java/com/android/server/connectivity/NetworkOfferTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity + +import android.net.INetworkOfferCallback +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.net.NetworkScore.KEEP_CONNECTED_NONE +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +const val POLICY_NONE = 0L + +@RunWith(AndroidJUnit4::class) +@SmallTest +class NetworkOfferTest { + val mockCallback = mock(INetworkOfferCallback::class.java) + + @Test + fun testOfferNeededUnneeded() { + val score = FullScore(50, POLICY_NONE, KEEP_CONNECTED_NONE) + val offer = NetworkOffer(score, NetworkCapabilities.Builder().build(), mockCallback, + 1 /* providerId */) + val request1 = mock(NetworkRequest::class.java) + val request2 = mock(NetworkRequest::class.java) + offer.onNetworkNeeded(request1) + verify(mockCallback).onNetworkNeeded(eq(request1)) + assertTrue(offer.neededFor(request1)) + assertFalse(offer.neededFor(request2)) + + offer.onNetworkNeeded(request2) + verify(mockCallback).onNetworkNeeded(eq(request2)) + assertTrue(offer.neededFor(request1)) + assertTrue(offer.neededFor(request2)) + + // Note that the framework never calls onNetworkNeeded multiple times with the same + // request without calling onNetworkUnneeded first. It would be incorrect usage and the + // behavior would be undefined, so there is nothing to test. + + offer.onNetworkUnneeded(request1) + verify(mockCallback).onNetworkUnneeded(eq(request1)) + assertFalse(offer.neededFor(request1)) + assertTrue(offer.neededFor(request2)) + + offer.onNetworkUnneeded(request2) + verify(mockCallback).onNetworkUnneeded(eq(request2)) + assertFalse(offer.neededFor(request1)) + assertFalse(offer.neededFor(request2)) + } +} |