[Revert^2] Reject TetheringRequests that fuzzy-match existing ones
Legacy behavior will return success results to multiple startTethering
calls of the same type, even though the link layer can only support one
of those requests at a time (other than tethered AP + LOHS).
For B+, clean up this behavior by only allowing the amount of requests
that the link layer can concurrently support. To do this, we'll
introduce the concept of "fuzzy-matching" such that if a new request
fuzzy-matches an existing request (either pending or serving), this
indicates that the link layer cannot support the new request
concurrently, and we'll fail the new request with
TETHER_ERROR_DUPLICATE_REQUEST.
To keep track of serving requests, we'll store the request and it's
IpServer in RequestTracker when we call IpServer.enable(), and we'll
remove it (indexed by IpServer) when
1) Calling IpServer.onStop()
2) Calling IpServer.unwanted()
3) We detect an IpServer transition to AvailableState with error code
Note: This was originally merged as aosp/3529632 but was reverted due to
a crash when reading aconfig flags at boot. That issue has been fixed
here.
Reverted changes: /q/submissionid:3541662-revert-3527440-JRJJTQBUFC
Bug: 216524590
Test: atest TetheringTest RequestTrackerTest TetheringManagerTest
Change-Id: I87a1ed31927cda27d3b7fdd7c823b8f828f8d777
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 0a66f01..2f9c3bc 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -37,6 +37,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -1258,6 +1259,44 @@
return sj.toString();
}
+ @SuppressLint("UnflaggedApi")
+ private static boolean supportsInterfaceName(int tetheringType) {
+ // TODO: Check the interface name for TETHERING_WIFI and TETHERING_WIFI_P2P once
+ // they're actually used.
+ // Suppress lint for TETHERING_VIRTUAL since this method is only used internally.
+ return tetheringType == TETHERING_VIRTUAL;
+ }
+
+ private static boolean supportsConcurrentConnectivityScopes(int tetheringType) {
+ // Currently, only WIFI supports simultaneous local and global connectivity.
+ // This can't happen for REQUEST_TYPE_EXPLICIT requests, because
+ // TetheringRequest.Builder will not allow building an explicit TetheringRequest
+ // with TETHERING_WIFI and CONNECTIVITY_SCOPE_LOCAL, but when local-only hotspot
+ // is running, there is a REQUEST_TYPE_IMPLICIT request in the serving request list.
+ return tetheringType == TETHERING_WIFI;
+ }
+
+ /**
+ * Returns true if the other TetheringRequest "fuzzy" matches this one. This is used
+ * internally to match tracked requests with external requests from API calls, and to reject
+ * additional requests that the link layer has no capacity for.
+ * @hide
+ */
+ public boolean fuzzyMatches(final TetheringRequest other) {
+ if (other == null) return false;
+ final int type = getTetheringType();
+ if (type != other.getTetheringType()) return false;
+ if (supportsInterfaceName(type)
+ && !TextUtils.equals(getInterfaceName(), other.getInterfaceName())) {
+ return false;
+ }
+ if (supportsConcurrentConnectivityScopes(type)
+ && getConnectivityScope() != other.getConnectivityScope()) {
+ return false;
+ }
+ return true;
+ }
+
/**
* @hide
*/
@@ -1279,6 +1318,8 @@
public boolean equalsIgnoreUidPackage(TetheringRequest otherRequest) {
TetheringRequestParcel parcel = getParcel();
TetheringRequestParcel otherParcel = otherRequest.getParcel();
+ // Note: Changes here should also be reflected in fuzzyMatches(TetheringRequest) when
+ // appropriate.
return parcel.requestType == otherParcel.requestType
&& parcel.tetheringType == otherParcel.tetheringType
&& Objects.equals(parcel.localIPv4Address, otherParcel.localIPv4Address)
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index d0ba431..70a3442 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -415,12 +415,6 @@
return mIpv4PrefixRequest;
}
- /** The TetheringRequest the IpServer started with. */
- @Nullable
- public TetheringRequest getTetheringRequest() {
- return mTetheringRequest;
- }
-
/**
* Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper
* thread.
diff --git a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
index 3ebe4f7..37ad86a 100644
--- a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
+++ b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
@@ -19,6 +19,8 @@
import static com.android.networkstack.tethering.util.TetheringUtils.createPlaceholderRequest;
import android.net.TetheringManager.TetheringRequest;
+import android.net.ip.IpServer;
+import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -28,6 +30,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Helper class to keep track of tethering requests.
@@ -36,29 +39,17 @@
* layer to start.
* 2) When the link layer is up, use {@link #getOrCreatePendingRequest(int)} to get a request to
* start IP serving with.
- * 3) Remove all pending requests with {@link #removeAllPendingRequests(int)}.
+ * 3) Remove pending request with {@link #removePendingRequest(TetheringRequest)}.
* Note: This class is not thread-safe.
- * TODO: Add the pending IIntResultListeners at the same time as the pending requests, and
- * call them when we get the tether result.
- * TODO: Add support for multiple Bluetooth requests before the PAN service connects instead of
- * using a separate mPendingPanRequestListeners.
- * TODO: Add support for fuzzy-matched requests.
*/
public class RequestTracker {
private static final String TAG = RequestTracker.class.getSimpleName();
- private class PendingRequest {
- @NonNull
- private final TetheringRequest mTetheringRequest;
+ @NonNull
+ private final boolean mUseFuzzyMatching;
- private PendingRequest(@NonNull TetheringRequest tetheringRequest) {
- mTetheringRequest = tetheringRequest;
- }
-
- @NonNull
- TetheringRequest getTetheringRequest() {
- return mTetheringRequest;
- }
+ public RequestTracker(boolean useFuzzyMatching) {
+ mUseFuzzyMatching = useFuzzyMatching;
}
public enum AddResult {
@@ -69,24 +60,51 @@
/**
* Failure indicating that the request could not be added due to a request of the same type
* with conflicting parameters already pending. If so, we must stop tethering for the
- * pending request before trying to add the result again.
+ * pending request before trying to add the result again. Only returned on V-.
*/
- FAILURE_CONFLICTING_PENDING_REQUEST
+ FAILURE_DUPLICATE_REQUEST_RESTART,
+ /**
+ * Failure indicating that the request could not be added due to a fuzzy-matched request
+ * already pending or serving. Only returned on B+.
+ */
+ FAILURE_DUPLICATE_REQUEST_ERROR,
}
/**
- * List of pending requests added by {@link #addPendingRequest(TetheringRequest)}. There can be
- * only one per type, since we remove every request of the same type when we add a request.
+ * List of pending requests added by {@link #addPendingRequest(TetheringRequest)}
+ * There can be only one per type, since we remove every request of the
+ * same type when we add a request.
*/
- private final List<PendingRequest> mPendingRequests = new ArrayList<>();
+ private final List<TetheringRequest> mPendingRequests = new ArrayList<>();
+ /**
+ * List of serving requests added by
+ * {@link #promoteRequestToServing(IpServer, TetheringRequest)}.
+ */
+ private final Map<IpServer, TetheringRequest> mServingRequests = new ArrayMap<>();
@VisibleForTesting
List<TetheringRequest> getPendingTetheringRequests() {
- List<TetheringRequest> requests = new ArrayList<>();
- for (PendingRequest pendingRequest : mPendingRequests) {
- requests.add(pendingRequest.getTetheringRequest());
+ return new ArrayList<>(mPendingRequests);
+ }
+
+ /**
+ * Adds a pending request or fails with FAILURE_CONFLICTING_REQUEST_FAIL if the request
+ * fuzzy-matches an existing request (either pending or serving).
+ */
+ public AddResult addPendingRequestFuzzyMatched(@NonNull final TetheringRequest newRequest) {
+ List<TetheringRequest> existingRequests = new ArrayList<>();
+ existingRequests.addAll(mServingRequests.values());
+ existingRequests.addAll(mPendingRequests);
+ for (TetheringRequest request : existingRequests) {
+ if (request.fuzzyMatches(newRequest)) {
+ Log.i(TAG, "Cannot add pending request due to existing fuzzy-matched "
+ + "request: " + request);
+ return AddResult.FAILURE_DUPLICATE_REQUEST_ERROR;
+ }
}
- return requests;
+
+ mPendingRequests.add(newRequest);
+ return AddResult.SUCCESS;
}
/**
@@ -95,9 +113,12 @@
* layer comes up. The result of the add operation will be returned as an AddResult code.
*/
public AddResult addPendingRequest(@NonNull final TetheringRequest newRequest) {
+ if (mUseFuzzyMatching) {
+ return addPendingRequestFuzzyMatched(newRequest);
+ }
+
// Check the existing requests to see if it is OK to add the new request.
- for (PendingRequest request : mPendingRequests) {
- TetheringRequest existingRequest = request.getTetheringRequest();
+ for (TetheringRequest existingRequest : mPendingRequests) {
if (existingRequest.getTetheringType() != newRequest.getTetheringType()) {
continue;
}
@@ -105,7 +126,7 @@
// Can't add request if there's a request of the same type with different
// parameters.
if (!existingRequest.equalsIgnoreUidPackage(newRequest)) {
- return AddResult.FAILURE_CONFLICTING_PENDING_REQUEST;
+ return AddResult.FAILURE_DUPLICATE_REQUEST_RESTART;
}
}
@@ -113,7 +134,7 @@
// conflicting parameters above, so these would have been equivalent anyway (except for
// UID).
removeAllPendingRequests(newRequest.getTetheringType());
- mPendingRequests.add(new PendingRequest(newRequest));
+ mPendingRequests.add(newRequest);
return AddResult.SUCCESS;
}
@@ -145,10 +166,8 @@
*/
@Nullable
public TetheringRequest getNextPendingRequest(int type) {
- for (PendingRequest pendingRequest : mPendingRequests) {
- TetheringRequest tetheringRequest =
- pendingRequest.getTetheringRequest();
- if (tetheringRequest.getTetheringType() == type) return tetheringRequest;
+ for (TetheringRequest request : mPendingRequests) {
+ if (request.getTetheringType() == type) return request;
}
return null;
}
@@ -158,7 +177,66 @@
*
* @param type Tethering type
*/
- public void removeAllPendingRequests(int type) {
- mPendingRequests.removeIf(r -> r.getTetheringRequest().getTetheringType() == type);
+ public void removeAllPendingRequests(final int type) {
+ mPendingRequests.removeIf(r -> r.getTetheringType() == type);
+ }
+
+ /**
+ * Removes a specific pending request.
+ *
+ * Note: For V-, this will be the same as removeAllPendingRequests to align with historical
+ * behavior.
+ *
+ * @param request Request to be removed
+ */
+ public void removePendingRequest(@NonNull TetheringRequest request) {
+ if (!mUseFuzzyMatching) {
+ // Remove all requests of the same type to match the historical behavior.
+ removeAllPendingRequests(request.getTetheringType());
+ return;
+ }
+
+ mPendingRequests.removeIf(r -> r.equals(request));
+ }
+
+ /**
+ * Removes a tethering request from the pending list and promotes it to serving with the
+ * IpServer that is using it.
+ * Note: If mUseFuzzyMatching is false, then the request will be removed from the pending list,
+ * but it will not be added to serving list.
+ */
+ public void promoteRequestToServing(@NonNull final IpServer ipServer,
+ @NonNull final TetheringRequest tetheringRequest) {
+ removePendingRequest(tetheringRequest);
+ if (!mUseFuzzyMatching) return;
+ mServingRequests.put(ipServer, tetheringRequest);
+ }
+
+
+ /**
+ * Returns the serving request tied to the given IpServer, or null if there is none.
+ * Note: If mUseFuzzyMatching is false, then this will always return null.
+ */
+ @Nullable
+ public TetheringRequest getServingRequest(@NonNull final IpServer ipServer) {
+ return mServingRequests.get(ipServer);
+ }
+
+ /**
+ * Removes the serving request tied to the given IpServer.
+ * Note: If mUseFuzzyMatching is false, then this is a no-op since serving requests are unused
+ * for that configuration.
+ */
+ public void removeServingRequest(@NonNull final IpServer ipServer) {
+ mServingRequests.remove(ipServer);
+ }
+
+ /**
+ * Removes all serving requests of the given tethering type.
+ *
+ * @param type Tethering type
+ */
+ public void removeAllServingRequests(final int type) {
+ mServingRequests.entrySet().removeIf(e -> e.getValue().getTetheringType() == type);
}
}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 1a26658..7b67095 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -43,6 +43,7 @@
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHERING_WIGIG;
import static android.net.TetheringManager.TETHER_ERROR_BLUETOOTH_SERVICE_PENDING;
+import static android.net.TetheringManager.TETHER_ERROR_DUPLICATE_REQUEST;
import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
@@ -67,6 +68,8 @@
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static com.android.networkstack.tethering.RequestTracker.AddResult.FAILURE_DUPLICATE_REQUEST_ERROR;
+import static com.android.networkstack.tethering.RequestTracker.AddResult.FAILURE_DUPLICATE_REQUEST_RESTART;
import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_FORCE_USB_FUNCTIONS;
import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
import static com.android.networkstack.tethering.UpstreamNetworkMonitor.isCellular;
@@ -309,7 +312,7 @@
mLooper = mDeps.makeTetheringLooper();
mNotificationUpdater = mDeps.makeNotificationUpdater(mContext, mLooper);
mTetheringMetrics = mDeps.makeTetheringMetrics(mContext);
- mRequestTracker = new RequestTracker();
+ mRequestTracker = new RequestTracker(isTetheringWithSoftApConfigEnabled());
mTetherStates = new ArrayMap<>();
mConnectedClientsTracker = new ConnectedClientsTracker();
@@ -460,6 +463,7 @@
return mSettingsObserver;
}
+ // TODO: Replace with SdkLevel.isAtLeastB() once the feature is fully implemented.
boolean isTetheringWithSoftApConfigEnabled() {
return SdkLevel.isAtLeastB() && Flags.tetheringWithSoftApConfig();
}
@@ -704,9 +708,16 @@
RequestTracker.AddResult result = mRequestTracker.addPendingRequest(request);
// If tethering is already pending with a conflicting request, stop tethering before
// starting.
- if (result == RequestTracker.AddResult.FAILURE_CONFLICTING_PENDING_REQUEST) {
+ if (result == FAILURE_DUPLICATE_REQUEST_RESTART) {
stopTetheringInternal(type); // Also removes the request from the tracker.
mRequestTracker.addPendingRequest(request);
+ } else if (result == FAILURE_DUPLICATE_REQUEST_ERROR) {
+ // Reject any fuzzy matched request.
+ // TODO: Add a CTS test to verify back-to-back start/stop calls succeed. This must
+ // be for a non-Wifi type, since Wifi will reject the start calls if it hasn't
+ // brought down the SoftAP yet.
+ sendTetherResult(listener, TETHER_ERROR_DUPLICATE_REQUEST);
+ return;
}
if (request.isExemptFromEntitlementCheck()) {
@@ -731,7 +742,8 @@
for (TetherState state : mTetherStates.values()) {
// TODO: isCurrentlyServing only starts returning true once the IpServer has processed
// the CMD_TETHER_REQUESTED. Ensure that we consider the request to be serving even when
- // that has not happened yet.
+ // that has not happened yet. This already works if the sync state machine is enabled
+ // (which includes all B+ devices).
if (state.isCurrentlyServing() && state.ipServer.interfaceType() == type) return true;
}
return false;
@@ -742,23 +754,24 @@
if (!isTetheringWithSoftApConfigEnabled()) return;
mHandler.post(() -> {
final int type = request.getTetheringType();
+ // TODO: when fuzzy-matching is enabled, check whether any pending request
+ // fuzzy-matches this request instead of just checking the type.
if (isTetheringTypePendingOrServing(type)) {
stopTetheringInternal(type);
- try {
- listener.onResult(TETHER_ERROR_NO_ERROR);
- } catch (RemoteException ignored) { }
+ // TODO: We should send the success result after the waiting for tethering to
+ // actually stop.
+ sendTetherResult(listener, TETHER_ERROR_NO_ERROR);
return;
}
// Request doesn't match any active requests, ignore.
- try {
- listener.onResult(TETHER_ERROR_UNKNOWN_REQUEST);
- } catch (RemoteException ignored) { }
+ sendTetherResult(listener, TETHER_ERROR_UNKNOWN_REQUEST);
});
}
void stopTetheringInternal(int type) {
mRequestTracker.removeAllPendingRequests(type);
+ mRequestTracker.removeAllServingRequests(type);
// Using a placeholder here is ok since none of the disable APIs use the request for
// anything. We simply need the tethering type to know which link layer to poke for removal.
@@ -802,21 +815,25 @@
// The result of Bluetooth tethering will be sent after the pan service connects.
if (result == TETHER_ERROR_BLUETOOTH_SERVICE_PENDING) return;
- sendTetherResult(listener, result, type);
+ sendTetherResultAndRemoveOnError(request, listener, result);
}
- private void sendTetherResult(final IIntResultListener listener, final int result,
- final int type) {
+ private void sendTetherResult(final IIntResultListener listener, final int result) {
if (listener != null) {
try {
listener.onResult(result);
- } catch (RemoteException e) { }
+ } catch (RemoteException e) {
+ }
}
+ }
- // If changing tethering fail, remove corresponding request
- // no matter who trigger the start/stop.
+ private void sendTetherResultAndRemoveOnError(TetheringRequest request,
+ final IIntResultListener listener, final int result) {
+ sendTetherResult(listener, result);
+
if (result != TETHER_ERROR_NO_ERROR) {
- mRequestTracker.removeAllPendingRequests(type);
+ mRequestTracker.removePendingRequest(request);
+ final int type = request.getTetheringType();
mTetheringMetrics.updateErrorCode(type, result);
mTetheringMetrics.sendReport(type);
}
@@ -862,8 +879,7 @@
// the service just to stop tethering since tethering is not started. Just remove
// any pending request to enable tethering, and notify them that they have failed.
if (mPendingPanRequestListener != null) {
- sendTetherResult(mPendingPanRequestListener, TETHER_ERROR_SERVICE_UNAVAIL,
- TETHERING_BLUETOOTH);
+ sendTetherResult(mPendingPanRequestListener, TETHER_ERROR_SERVICE_UNAVAIL);
}
mPendingPanRequestListener = null;
return TETHER_ERROR_NO_ERROR;
@@ -904,7 +920,10 @@
if (mPendingPanRequestListener != null) {
final int result = setBluetoothTetheringSettings(mBluetoothPan,
true /* enable */);
- sendTetherResult(mPendingPanRequestListener, result, TETHERING_BLUETOOTH);
+ sendTetherResultAndRemoveOnError(
+ mRequestTracker.getOrCreatePendingRequest(TETHERING_BLUETOOTH),
+ mPendingPanRequestListener,
+ result);
}
mPendingPanRequestListener = null;
});
@@ -918,8 +937,10 @@
mIsConnected = false;
if (mPendingPanRequestListener != null) {
- sendTetherResult(mPendingPanRequestListener, TETHER_ERROR_SERVICE_UNAVAIL,
- TETHERING_BLUETOOTH);
+ sendTetherResultAndRemoveOnError(
+ mRequestTracker.getOrCreatePendingRequest(TETHERING_BLUETOOTH),
+ mPendingPanRequestListener,
+ TETHER_ERROR_SERVICE_UNAVAIL);
}
mPendingPanRequestListener = null;
mBluetoothIfaceRequest = null;
@@ -1093,9 +1114,7 @@
final int type = ifaceNameToType(iface);
if (type == TETHERING_INVALID) {
Log.e(TAG, "Ignoring call to legacy tether for unknown iface " + iface);
- try {
- listener.onResult(TETHER_ERROR_UNKNOWN_IFACE);
- } catch (RemoteException e) { }
+ sendTetherResult(listener, TETHER_ERROR_UNKNOWN_IFACE);
}
TetheringRequest request = mRequestTracker.getNextPendingRequest(type);
@@ -1132,9 +1151,7 @@
// Do nothing
break;
}
- try {
- listener.onResult(result);
- } catch (RemoteException e) { }
+ sendTetherResult(listener, result);
}
/**
@@ -1167,11 +1184,9 @@
Log.e(TAG, "Tried to Tether an unavailable iface: " + iface + ", ignoring");
return TETHER_ERROR_UNAVAIL_IFACE;
}
+ mRequestTracker.promoteRequestToServing(tetherState.ipServer, request);
// NOTE: If a CMD_TETHER_REQUESTED message is already in the IpServer's queue but not yet
// processed, this will be a no-op and it will not return an error.
- //
- // This code cannot race with untether() because they both run on the handler thread.
- mRequestTracker.removeAllPendingRequests(request.getTetheringType());
tetherState.ipServer.enable(request);
if (request.getRequestType() == REQUEST_TYPE_PLACEHOLDER) {
TerribleErrorLog.logTerribleError(TetheringStatsLog::write,
@@ -1191,10 +1206,7 @@
return;
}
mHandler.post(() -> {
- try {
- listener.onResult(legacyUntetherInternal(iface));
- } catch (RemoteException e) {
- }
+ sendTetherResult(listener, legacyUntetherInternal(iface));
});
}
@@ -1209,7 +1221,7 @@
Log.e(TAG, "Tried to untether an inactive iface :" + iface + ", ignoring");
return TETHER_ERROR_UNAVAIL_IFACE;
}
- tetherState.ipServer.unwanted();
+ ensureIpServerUnwanted(tetherState.ipServer);
return TETHER_ERROR_NO_ERROR;
}
@@ -1289,7 +1301,12 @@
final TetherState tetherState = mTetherStates.valueAt(i);
final int type = tetherState.ipServer.interfaceType();
final String iface = mTetherStates.keyAt(i);
- final TetheringRequest request = tetherState.ipServer.getTetheringRequest();
+ // Note: serving requests are only populated on B+. B+ also uses the sync state
+ // machine by default. This ensures that the serving request is (correctly) populated
+ // after the IpServer enters the available state and before it enters the serving
+ // state.
+ final TetheringRequest request =
+ mRequestTracker.getServingRequest(tetherState.ipServer);
final boolean includeSoftApConfig = request != null && cookie != null
&& (cookie.uid == request.getUid() || cookie.hasSystemPrivilege);
final TetheringInterface tetheringIface = new TetheringInterface(type, iface,
@@ -1611,7 +1628,7 @@
private void disableWifiIpServingCommon(int tetheringType, String ifname) {
if (!TextUtils.isEmpty(ifname) && mTetherStates.containsKey(ifname)) {
- mTetherStates.get(ifname).ipServer.unwanted();
+ ensureIpServerUnwanted(mTetherStates.get(ifname).ipServer);
return;
}
@@ -1628,7 +1645,7 @@
for (int i = 0; i < mTetherStates.size(); i++) {
final IpServer ipServer = mTetherStates.valueAt(i).ipServer;
if (ipServer.interfaceType() == tetheringType) {
- ipServer.unwanted();
+ ensureIpServerUnwanted(ipServer);
return;
}
}
@@ -1775,9 +1792,7 @@
void setUsbTethering(boolean enable, IIntResultListener listener) {
mHandler.post(() -> {
- try {
- listener.onResult(setUsbTethering(enable));
- } catch (RemoteException e) { }
+ sendTetherResult(listener, setUsbTethering(enable));
});
}
@@ -2904,6 +2919,9 @@
tetherState.lastState = state;
tetherState.lastError = lastError;
} else {
+ // Note: Even if an IpServer exists for this iface, it may be different from "who"
+ // if a new IpServer fills the gap before the IpServer.STATE_UNAVAILABLE transition.
+ // TODO: remove this comment once the sync state machine is enabled everywhere.
if (DBG) Log.d(TAG, "got notification from stale iface " + iface);
}
@@ -2918,7 +2936,19 @@
int which;
switch (state) {
case IpServer.STATE_UNAVAILABLE:
+ which = TetherMainSM.EVENT_IFACE_SERVING_STATE_INACTIVE;
+ break;
case IpServer.STATE_AVAILABLE:
+ if (lastError != TETHER_ERROR_NO_ERROR) {
+ // IpServer transitioned from an enabled state (STATE_TETHERED or
+ // STATE_LOCAL_ONLY) back to STATE_AVAILABLE due to an error, so make sure
+ // we remove the serving request from RequestTracker.
+ // TODO: don't continue to use IpServers after they have hit an error, and
+ // instead move them to STATE_UNAVAILABLE. This code can then
+ // unconditionally remove the serving request whenever the IpServer enters
+ // STATE_UNAVAILABLE.
+ mRequestTracker.removeServingRequest(who);
+ }
which = TetherMainSM.EVENT_IFACE_SERVING_STATE_INACTIVE;
break;
case IpServer.STATE_TETHERED:
@@ -3014,11 +3044,18 @@
final TetherState tetherState = mTetherStates.get(iface);
if (tetherState == null) return;
+ mRequestTracker.removeServingRequest(tetherState.ipServer);
tetherState.ipServer.stop();
mLog.i("removing IpServer for: " + iface);
mTetherStates.remove(iface);
}
+ private void ensureIpServerUnwanted(final IpServer ipServer) {
+ mLog.i("unrequesting IpServer: " + ipServer);
+ mRequestTracker.removeServingRequest(ipServer);
+ ipServer.unwanted();
+ }
+
private static String[] copy(String[] strarray) {
return Arrays.copyOf(strarray, strarray.length);
}
@@ -3026,9 +3063,7 @@
void setPreferTestNetworks(final boolean prefer, IIntResultListener listener) {
mHandler.post(() -> {
mUpstreamNetworkMonitor.setPreferTestNetworks(prefer);
- try {
- listener.onResult(TETHER_ERROR_NO_ERROR);
- } catch (RemoteException e) { }
+ sendTetherResult(listener, TETHER_ERROR_NO_ERROR);
});
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/RequestTrackerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/RequestTrackerTest.java
index e00e9f0..086f2d2 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/RequestTrackerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/RequestTrackerTest.java
@@ -18,19 +18,22 @@
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static com.android.networkstack.tethering.util.TetheringUtils.createPlaceholderRequest;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+
import android.net.TetheringManager.TetheringRequest;
+import android.net.ip.IpServer;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.networkstack.tethering.RequestTracker.AddResult;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,13 +42,10 @@
public class RequestTrackerTest {
private RequestTracker mRequestTracker;
- @Before
- public void setUp() {
- mRequestTracker = new RequestTracker();
- }
-
@Test
public void testNoRequestsAdded_noPendingRequests() {
+ mRequestTracker = new RequestTracker(false);
+
assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
.isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
@@ -53,6 +53,7 @@
@Test
public void testAddRequest_successResultAndBecomesNextPending() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
final AddResult result = mRequestTracker.addPendingRequest(request);
@@ -64,6 +65,7 @@
@Test
public void testAddRequest_equalRequestExists_successResultAndBecomesNextPending() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
mRequestTracker.addPendingRequest(request);
@@ -77,6 +79,7 @@
@Test
public void testAddRequest_equalButDifferentUidRequest_successResultAndBecomesNextPending() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
request.setUid(1000);
request.setPackageName("package");
@@ -94,7 +97,8 @@
}
@Test
- public void testAddConflictingRequest_returnsFailureConflictingPendingRequest() {
+ public void testAddRequest_conflictingPendingRequest_returnsFailureConflictingRequestRestart() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
final TetheringRequest conflictingRequest = new TetheringRequest.Builder(TETHERING_WIFI)
.setExemptFromEntitlementCheck(true).build();
@@ -102,13 +106,139 @@
final AddResult result = mRequestTracker.addPendingRequest(conflictingRequest);
- assertThat(result).isEqualTo(AddResult.FAILURE_CONFLICTING_PENDING_REQUEST);
+ assertThat(result).isEqualTo(AddResult.FAILURE_DUPLICATE_REQUEST_RESTART);
assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
}
@Test
+ public void testAddRequest_noExistingRequestsFuzzyMatching_returnsSuccess() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+
+ final AddResult result = mRequestTracker.addPendingRequest(request);
+
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ }
+
+ @Test
+ public void testAddRequest_conflictingPendingRequestFuzzyMatching_returnsFailure() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ final TetheringRequest conflictingRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setExemptFromEntitlementCheck(true).build();
+ mRequestTracker.addPendingRequest(request);
+
+ final AddResult result = mRequestTracker.addPendingRequest(conflictingRequest);
+
+ assertThat(result).isEqualTo(AddResult.FAILURE_DUPLICATE_REQUEST_ERROR);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ }
+
+ @Test
+ public void testAddRequest_conflictingServingRequestFuzzyMatching_returnsFailure() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ final TetheringRequest conflictingRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setExemptFromEntitlementCheck(true).build();
+ mRequestTracker.promoteRequestToServing(mock(IpServer.class), request);
+
+ final AddResult result = mRequestTracker.addPendingRequest(conflictingRequest);
+
+ assertThat(result).isEqualTo(AddResult.FAILURE_DUPLICATE_REQUEST_ERROR);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
+ .isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
+ }
+
+ @Test
+ public void testAddRequest_nonMatchingPendingRequestFuzzyMatching_returnsSuccess() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_VIRTUAL).build();
+ final TetheringRequest nonFuzzyMatched = new TetheringRequest.Builder(TETHERING_VIRTUAL)
+ .setInterfaceName("iface")
+ .build();
+ mRequestTracker.addPendingRequest(request);
+
+ final AddResult result = mRequestTracker.addPendingRequest(nonFuzzyMatched);
+
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ // Next request is still the first, but verify RequestTracker contains the second request by
+ // seeing if it rejects anything matching the second request
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_VIRTUAL)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_VIRTUAL))
+ .isEqualTo(request);
+ assertThat(mRequestTracker.addPendingRequestFuzzyMatched(nonFuzzyMatched))
+ .isEqualTo(AddResult.FAILURE_DUPLICATE_REQUEST_ERROR);
+ }
+
+ @Test
+ public void testAddRequest_nonMatchingServingRequestFuzzyMatching_returnsSuccess() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_VIRTUAL).build();
+ final TetheringRequest nonFuzzyMatched = new TetheringRequest.Builder(TETHERING_VIRTUAL)
+ .setInterfaceName("iface")
+ .build();
+ mRequestTracker.promoteRequestToServing(mock(IpServer.class), request);
+
+ final AddResult result = mRequestTracker.addPendingRequest(nonFuzzyMatched);
+
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_VIRTUAL))
+ .isEqualTo(nonFuzzyMatched);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_VIRTUAL))
+ .isEqualTo(nonFuzzyMatched);
+ }
+
+ @Test
+ public void testRemovePendingRequest_removesAllPendingRequestsOfType() {
+ mRequestTracker = new RequestTracker(false);
+ final TetheringRequest request1 = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ request1.setUid(1000);
+ request1.setPackageName("package");
+ mRequestTracker.addPendingRequest(request1);
+ final TetheringRequest request2 = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ request2.setUid(2000);
+ request2.setPackageName("package2");
+
+ mRequestTracker.removePendingRequest(request2);
+
+ // Verify request1 isn't pending even though we tried to remove a different request
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
+ .isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
+ }
+
+ @Test
+ public void testRemovePendingRequest_fuzzyMatching_onlyTheEqualRequestIsRemoved() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request1 = new TetheringRequest.Builder(TETHERING_VIRTUAL).build();
+ final TetheringRequest request2 = new TetheringRequest.Builder(TETHERING_VIRTUAL)
+ .setInterfaceName("iface")
+ .build();
+ mRequestTracker.addPendingRequest(request1);
+ mRequestTracker.addPendingRequest(request2);
+
+ mRequestTracker.removePendingRequest(request2);
+
+ // Verify request1 is still pending.
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_VIRTUAL)).isEqualTo(request1);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_VIRTUAL))
+ .isEqualTo(request1);
+ assertThat(mRequestTracker.addPendingRequestFuzzyMatched(request1))
+ .isEqualTo(AddResult.FAILURE_DUPLICATE_REQUEST_ERROR);
+ // Verify we've removed request2 by checking if it can be added back without
+ // FAILURE_CONFLICTING_REQUEST_FAIL.
+ assertThat(mRequestTracker.addPendingRequestFuzzyMatched(request2))
+ .isEqualTo(AddResult.SUCCESS);
+ }
+
+ @Test
public void testRemoveAllPendingRequests_noPendingRequestsLeft() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest firstRequest = new TetheringRequest.Builder(TETHERING_WIFI).build();
firstRequest.setUid(1000);
firstRequest.setPackageName("package");
@@ -127,6 +257,7 @@
@Test
public void testRemoveAllPendingRequests_differentTypeExists_doesNotRemoveDifferentType() {
+ mRequestTracker = new RequestTracker(false);
final TetheringRequest differentType = new TetheringRequest.Builder(TETHERING_USB).build();
mRequestTracker.addPendingRequest(differentType);
@@ -136,4 +267,61 @@
assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_USB))
.isEqualTo(differentType);
}
+
+ @Test
+ public void testPromoteRequestToServing_requestIsntPendingAnymore() {
+ mRequestTracker = new RequestTracker(false);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ mRequestTracker.addPendingRequest(request);
+
+ mRequestTracker.promoteRequestToServing(mock(IpServer.class), request);
+
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
+ .isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
+ }
+
+ @Test
+ public void testPromoteRequestToServing_fuzzyMatching_requestIsntPendingAnymore() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ mRequestTracker.addPendingRequest(request);
+
+ mRequestTracker.promoteRequestToServing(mock(IpServer.class), request);
+
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
+ .isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
+ }
+
+ @Test
+ public void testRemoveServingRequest_fuzzyMatching_requestCanBeAddedAgain() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ mRequestTracker.addPendingRequest(request);
+ IpServer ipServer = mock(IpServer.class);
+ mRequestTracker.promoteRequestToServing(ipServer, request);
+
+ mRequestTracker.removeServingRequest(ipServer);
+
+ AddResult result = mRequestTracker.addPendingRequest(request);
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ }
+
+ @Test
+ public void testRemoveAllServingRequests_fuzzyMatching_requestCanBeAddedAgain() {
+ mRequestTracker = new RequestTracker(true);
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ mRequestTracker.addPendingRequest(request);
+ mRequestTracker.promoteRequestToServing(mock(IpServer.class), request);
+
+ mRequestTracker.removeAllServingRequests(TETHERING_WIFI);
+
+ AddResult result = mRequestTracker.addPendingRequest(request);
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index fe3b201..74bfec7 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -50,6 +50,7 @@
import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_DUPLICATE_REQUEST;
import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
@@ -59,6 +60,7 @@
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -93,6 +95,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.notNull;
@@ -197,6 +200,7 @@
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.flags.Flags;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.PrivateAddressCoordinator;
@@ -308,8 +312,7 @@
@Mock private TetheringMetrics mTetheringMetrics;
@Mock private PrivateAddressCoordinator.Dependencies mPrivateAddressCoordinatorDependencies;
- private final MockIpServerDependencies mIpServerDependencies =
- spy(new MockIpServerDependencies());
+ private MockIpServerDependencies mIpServerDependencies;
private final MockTetheringDependencies mTetheringDependencies =
new MockTetheringDependencies();
@@ -396,6 +399,9 @@
}
public class MockIpServerDependencies extends IpServer.Dependencies {
+
+ private int mOnDhcpServerCreatedResult = STATUS_SUCCESS;
+
@Override
public DadProxy getDadProxy(
Handler handler, InterfaceParams ifParams) {
@@ -437,7 +443,7 @@
DhcpServerCallbacks cb) {
new Thread(() -> {
try {
- cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer);
+ cb.onDhcpServerCreated(mOnDhcpServerCreatedResult, mDhcpServer);
} catch (RemoteException e) {
fail(e.getMessage());
}
@@ -448,6 +454,10 @@
IpNeighborMonitor.NeighborEventConsumer c) {
return mIpNeighborMonitor;
}
+
+ public void setOnDhcpServerCreatedResult(final int result) {
+ mOnDhcpServerCreatedResult = result;
+ }
}
public class MockTetheringDependencies extends TetheringDependencies {
@@ -708,6 +718,7 @@
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true);
+ mIpServerDependencies = spy(new MockIpServerDependencies());
}
// In order to interact with syncSM from the test, tethering must be created in test thread.
@@ -2425,7 +2436,7 @@
@Test
public void testSoftApConfigInTetheringEventCallback() throws Exception {
- assumeTrue(SdkLevel.isAtLeastV());
+ assumeTrue(isTetheringWithSoftApConfigEnabled());
when(mContext.checkCallingOrSelfPermission(NETWORK_SETTINGS))
.thenReturn(PERMISSION_DENIED);
when(mContext.checkCallingOrSelfPermission(NETWORK_STACK))
@@ -2482,14 +2493,12 @@
// Enable wifi tethering
mBinderCallingUid = TEST_CALLER_UID;
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, null);
+ mLooper.dispatchAll();
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
mLooper.dispatchAll();
- if (SdkLevel.isAtLeastB()) {
- // Starting in B, ignore the interfaceStatusChanged
- callback.assertNoStateChangeCallback();
- }
+ // Netd "up" event should not trigger a state change callback in B+.
+ callback.assertNoStateChangeCallback();
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
- mLooper.dispatchAll();
// Verify we see Available -> Tethered states
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
callback.pollTetherStatesChanged().availableList);
@@ -2507,20 +2516,15 @@
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED);
// Disable wifi tethering
- mLooper.dispatchAll();
mTethering.stopTethering(TETHERING_WIFI);
- sendWifiApStateChanged(WIFI_AP_STATE_DISABLED);
- if (isAtLeastT()) {
- // After T, tethering doesn't support WIFI_AP_STATE_DISABLED with null interface name.
- callback.assertNoStateChangeCallback();
- sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
+ mLooper.dispatchAll();
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME,
IFACE_IP_MODE_TETHERED);
- }
- assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
+ assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
callback.pollTetherStatesChanged().availableList);
assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
differentCallback.pollTetherStatesChanged().availableList);
- assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
+ assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
settingsCallback.pollTetherStatesChanged().availableList);
mLooper.dispatchAll();
callback.expectUpstreamChanged(NULL_NETWORK);
@@ -2528,6 +2532,253 @@
callback.assertNoCallback();
}
+ private boolean isTetheringWithSoftApConfigEnabled() {
+ return SdkLevel.isAtLeastB() && Flags.tetheringWithSoftApConfig();
+ }
+
+ @Test
+ public void testFuzzyMatchedWifiCannotBeAdded() throws Exception {
+ assumeTrue(isTetheringWithSoftApConfigEnabled());
+ initTetheringOnTestThread();
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringInterface wifiIfaceWithoutConfig = new TetheringInterface(
+ TETHERING_WIFI, TEST_WLAN_IFNAME, null);
+ final TetheringInterface wifiIfaceWithConfig = new TetheringInterface(
+ TETHERING_WIFI, TEST_WLAN_IFNAME, softApConfig);
+
+ // Register callback before running any tethering.
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ callback.expectTetheredClientChanged(Collections.emptyList());
+ callback.expectUpstreamChanged(NULL_NETWORK);
+ callback.expectConfigurationChanged(
+ mTethering.getTetheringConfiguration().toStableParcelable());
+ assertTetherStatesNotNullButEmpty(callback.pollTetherStatesChanged());
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+ UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
+ initTetheringUpstream(upstreamState);
+
+ // Start wifi tethering but don't trigger the link layer event yet.
+ mBinderCallingUid = TEST_CALLER_UID;
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ tetheringRequest.setUid(TEST_CALLER_UID);
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+
+ // Try starting wifi tethering with various fuzzy-matching requests and verify we get
+ // TETHER_ERROR_DUPLICATE_REQUEST.
+
+ // Different static IP addresses
+ final TetheringRequest differentIpAddr = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig)
+ .setStaticIpv4Addresses(new LinkAddress("192.168.0.123/24"),
+ new LinkAddress("192.168.0.42/24"))
+ .build();
+ differentIpAddr.setUid(TEST_CALLER_UID);
+ ResultListener differentIpAddrListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(differentIpAddr, TEST_CALLER_PKG, differentIpAddrListener);
+ mLooper.dispatchAll();
+ verify(mWifiManager, times(1)).startTetheredHotspot(any());
+ verify(mWifiManager, never()).stopSoftAp();
+ differentIpAddrListener.assertHasResult();
+
+ // Different UID
+ final TetheringRequest differentUid = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ differentUid.setUid(TEST_CALLER_UID + 1);
+ ResultListener differentUidListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(differentUid, TEST_CALLER_PKG, differentUidListener);
+ mLooper.dispatchAll();
+ differentUidListener.assertHasResult();
+ verify(mWifiManager, times(1)).startTetheredHotspot(any());
+ verify(mWifiManager, never()).stopSoftAp();
+
+ // Mock the link layer event to start IP serving and verify we still get
+ // TETHER_ERROR_DUPLICATE_REQUEST even though the request is no longer pending and is
+ // already serving.
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+ assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
+ callback.pollTetherStatesChanged().availableList);
+ assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
+ callback.pollTetherStatesChanged().tetheredList);
+ callback.expectUpstreamChanged(upstreamState.network);
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED);
+ differentIpAddrListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ differentUidListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(differentIpAddr, TEST_CALLER_PKG, differentIpAddrListener);
+ mTethering.startTethering(differentUid, TEST_CALLER_PKG, differentUidListener);
+ mLooper.dispatchAll();
+ differentIpAddrListener.assertHasResult();
+ differentUidListener.assertHasResult();
+ verify(mWifiManager, times(1)).startTetheredHotspot(any());
+ verify(mWifiManager, never()).stopSoftAp();
+ }
+
+ @Test
+ public void testFuzzyMatchedWifiCanBeAddedAfterIpServerStopped() throws Exception {
+ assumeTrue(isTetheringWithSoftApConfigEnabled());
+ when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
+ initTetheringOnTestThread();
+
+ // Start wifi tethering and mock the ap state change.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+
+ // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
+ ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, failureListener);
+ mLooper.dispatchAll();
+ failureListener.assertHasResult();
+
+ // Trigger Netd callback to stop the IpServer
+ mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, false);
+
+ // We should be able to request the same Wifi again
+ ResultListener successListener2 = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
+ mLooper.dispatchAll();
+ successListener2.assertHasResult();
+ }
+
+ @Test
+ public void testFuzzyMatchedWifiCanBeAddedAfterIpServerUnwanted() throws Exception {
+ assumeTrue(isTetheringWithSoftApConfigEnabled());
+ when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
+ initTetheringOnTestThread();
+
+ // Start wifi tethering and mock the ap state change.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+
+ // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
+ ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, failureListener);
+ mLooper.dispatchAll();
+ failureListener.assertHasResult();
+
+ // Trigger wifi ap state change to tell IpServer it's unwanted.
+ sendWifiApStateChanged(WIFI_AP_STATE_DISABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+
+ // We should be able to request the same Wifi again
+ ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
+ mLooper.dispatchAll();
+ successListener2.assertHasResult();
+ }
+
+ @Test
+ public void testFuzzyMatchedWifiCanBeAddedAfterIpServerError() throws Exception {
+ assumeTrue(isTetheringWithSoftApConfigEnabled());
+ when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
+ initTetheringOnTestThread();
+
+ // Set up the DHCP server to fail creation.
+ mIpServerDependencies.setOnDhcpServerCreatedResult(STATUS_UNKNOWN_ERROR);
+
+ // Start wifi tethering and mock the ap state change.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+
+ // We should be able to request the same Wifi again since the DHCP server transitioned the
+ // IpServer back to InitialState.
+ ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
+ mLooper.dispatchAll();
+ successListener2.assertHasResult();
+ }
+
+ @Test
+ public void testFuzzyMatchedWifiCanBeAddedAfterStoppingPendingRequest() throws Exception {
+ assumeTrue(isTetheringWithSoftApConfigEnabled());
+ when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
+ initTetheringOnTestThread();
+
+ // Start wifi tethering but keep the request pending by not sending the ap state change.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+
+ // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
+ ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, failureListener);
+ mLooper.dispatchAll();
+ failureListener.assertHasResult();
+
+ // Stop Wifi tethering.
+ mTethering.stopTethering(TETHERING_WIFI);
+
+ // We should be able to request the same Wifi again
+ ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
+ mLooper.dispatchAll();
+ successListener2.assertHasResult();
+ }
+
+ @Test
+ public void testFuzzyMatchedWifiCanBeAddedAfterStoppingServingRequest() throws Exception {
+ assumeTrue(isTetheringWithSoftApConfigEnabled());
+ when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
+ initTetheringOnTestThread();
+
+ // Start wifi tethering and mock the ap state change.
+ SoftApConfiguration softApConfig = new SoftApConfiguration.Builder().setWifiSsid(
+ WifiSsid.fromBytes("SoftApConfig".getBytes(StandardCharsets.UTF_8))).build();
+ final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setSoftApConfiguration(softApConfig).build();
+ ResultListener successListener = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener);
+ mLooper.dispatchAll();
+ successListener.assertHasResult();
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+
+ // Starting wifi again will cause TETHER_ERROR_DUPLICATE_REQUEST
+ ResultListener failureListener = new ResultListener(TETHER_ERROR_DUPLICATE_REQUEST);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, failureListener);
+ mLooper.dispatchAll();
+ failureListener.assertHasResult();
+
+ // Stop Wifi tethering.
+ mTethering.stopTethering(TETHERING_WIFI);
+
+ // We should be able to request the same Wifi again
+ ResultListener successListener2 = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, successListener2);
+ mLooper.dispatchAll();
+ successListener2.assertHasResult();
+ }
+
@Test
public void testReportFailCallbackIfOffloadNotSupported() throws Exception {
initTetheringOnTestThread();
@@ -2794,7 +3045,8 @@
}
@Test
- public void testMultipleStartTethering() throws Exception {
+ public void testMultipleStartTetheringLegacy() throws Exception {
+ assumeFalse(isTetheringWithSoftApConfigEnabled());
initTetheringOnTestThread();
final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24");
final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24");
@@ -3575,6 +3827,7 @@
@Test
public void testStartBluetoothTetheringFailsWhenTheresAnExistingRequestWaitingForPanService()
throws Exception {
+ assumeFalse(isTetheringWithSoftApConfigEnabled());
initTetheringOnTestThread();
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index e645f67..52799ab 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -32,6 +32,7 @@
import static android.net.TetheringManager.TETHERING_VIRTUAL;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_DUPLICATE_REQUEST;
import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
@@ -234,6 +235,30 @@
}
+ @Test
+ public void testStartTetheringDuplicateRequestRejected() throws Exception {
+ assumeTrue(isTetheringWithSoftApConfigEnabled());
+ final TestTetheringEventCallback tetherEventCallback =
+ mCtsTetheringUtils.registerTetheringEventCallback();
+ try {
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ tetherEventCallback.expectNoTetheringActive();
+
+ final String[] wifiRegexs = mTM.getTetherableWifiRegexs();
+ mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
+ mTetherChangeReceiver.expectTethering(true /* active */, wifiRegexs);
+
+ final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+ runAsShell(TETHER_PRIVILEGED, () -> {
+ mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
+ c -> c.run() /* executor */, startTetheringCallback);
+ startTetheringCallback.expectTetheringFailed(TETHER_ERROR_DUPLICATE_REQUEST);
+ });
+ } finally {
+ mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+ }
+ }
+
private SoftApConfiguration createSoftApConfiguration(@NonNull String ssid) {
SoftApConfiguration config;
if (SdkLevel.isAtLeastT()) {
@@ -380,17 +405,20 @@
tetherEventCallback.assumeWifiTetheringSupported(mContext);
tetherEventCallback.expectNoTetheringActive();
- SoftApConfiguration softApConfig = createSoftApConfiguration("SSID");
+ SoftApConfiguration softApConfig = isTetheringWithSoftApConfigEnabled()
+ ? createSoftApConfiguration("SSID") : null;
final TetheringInterface tetheredIface =
mCtsTetheringUtils.startWifiTethering(tetherEventCallback, softApConfig);
assertNotNull(tetheredIface);
- assertEquals(softApConfig, tetheredIface.getSoftApConfiguration());
- final String wifiTetheringIface = tetheredIface.getInterface();
+ if (isTetheringWithSoftApConfigEnabled()) {
+ assertEquals(softApConfig, tetheredIface.getSoftApConfiguration());
+ }
mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);
if (!SdkLevel.isAtLeastB()) {
+ final String wifiTetheringIface = tetheredIface.getInterface();
try {
final int ret = runAsShell(TETHER_PRIVILEGED,
() -> mTM.tether(wifiTetheringIface));