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 Bug: 216524590 Test: atest TetheringTest RequestTrackerTest TetheringManagerTest Change-Id: I1e193f3595a48f806fb70fd08630a12ce2631a59
This commit is contained in:
parent
b453055426
commit
2add9260ef
7 changed files with 733 additions and 116 deletions
|
|
@ -37,6 +37,7 @@ import android.os.Parcelable;
|
|||
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 @@ public class TetheringManager {
|
|||
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 class TetheringManager {
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -415,12 +415,6 @@ public class IpServer extends StateMachineShim {
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ package com.android.networkstack.tethering;
|
|||
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 com.android.internal.annotations.VisibleForTesting;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class to keep track of tethering requests.
|
||||
|
|
@ -36,29 +39,17 @@ import java.util.List;
|
|||
* 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 @@ public class RequestTracker {
|
|||
/**
|
||||
* 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 @@ public class RequestTracker {
|
|||
* 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 @@ public class RequestTracker {
|
|||
// 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 @@ public class RequestTracker {
|
|||
// 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 @@ public class RequestTracker {
|
|||
*/
|
||||
@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 @@ public class RequestTracker {
|
|||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import static android.net.TetheringManager.TETHERING_WIFI;
|
|||
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.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
|
|||
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 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
return mSettingsObserver;
|
||||
}
|
||||
|
||||
// TODO: Replace with SdkLevel.isAtLeastB() once the feature is fully implemented.
|
||||
boolean isTetheringWithSoftApConfigEnabled() {
|
||||
return SdkLevel.isAtLeastB() && Flags.tetheringWithSoftApConfig();
|
||||
}
|
||||
|
|
@ -704,9 +708,16 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
// 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendTetherResultAndRemoveOnError(TetheringRequest request,
|
||||
final IIntResultListener listener, final int result) {
|
||||
sendTetherResult(listener, result);
|
||||
|
||||
// If changing tethering fail, remove corresponding request
|
||||
// no matter who trigger the start/stop.
|
||||
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 @@ public class Tethering {
|
|||
// 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 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
// Do nothing
|
||||
break;
|
||||
}
|
||||
try {
|
||||
listener.onResult(result);
|
||||
} catch (RemoteException e) { }
|
||||
sendTetherResult(listener, result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1167,11 +1184,9 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
return;
|
||||
}
|
||||
mHandler.post(() -> {
|
||||
try {
|
||||
listener.onResult(legacyUntetherInternal(iface));
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
sendTetherResult(listener, legacyUntetherInternal(iface));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1209,7 +1221,7 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
|
||||
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 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
|
||||
void setUsbTethering(boolean enable, IIntResultListener listener) {
|
||||
mHandler.post(() -> {
|
||||
try {
|
||||
listener.onResult(setUsbTethering(enable));
|
||||
} catch (RemoteException e) { }
|
||||
sendTetherResult(listener, setUsbTethering(enable));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2904,6 +2919,9 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
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 @@ public class Tethering {
|
|||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,19 +18,22 @@ package com.android.networkstack.tethering;
|
|||
|
||||
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 @@ import org.junit.runner.RunWith;
|
|||
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 @@ public class RequestTrackerTest {
|
|||
|
||||
@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 @@ public class RequestTrackerTest {
|
|||
|
||||
@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 @@ public class RequestTrackerTest {
|
|||
|
||||
@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 @@ public class RequestTrackerTest {
|
|||
}
|
||||
|
||||
@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 @@ public class RequestTrackerTest {
|
|||
|
||||
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 @@ public class RequestTrackerTest {
|
|||
|
||||
@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 @@ public class RequestTrackerTest {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import static android.net.TetheringManager.TETHERING_USB;
|
|||
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_FAILED;
|
|||
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.assertNotNull;
|
|||
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 androidx.test.runner.AndroidJUnit4;
|
|||
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 @@ public class TetheringTest {
|
|||
@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 TetheringTest {
|
|||
}
|
||||
|
||||
public class MockIpServerDependencies extends IpServer.Dependencies {
|
||||
|
||||
private int mOnDhcpServerCreatedResult = STATUS_SUCCESS;
|
||||
|
||||
@Override
|
||||
public DadProxy getDadProxy(
|
||||
Handler handler, InterfaceParams ifParams) {
|
||||
|
|
@ -437,7 +443,7 @@ public class TetheringTest {
|
|||
DhcpServerCallbacks cb) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer);
|
||||
cb.onDhcpServerCreated(mOnDhcpServerCreatedResult, mDhcpServer);
|
||||
} catch (RemoteException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
|
@ -448,6 +454,10 @@ public class TetheringTest {
|
|||
IpNeighborMonitor.NeighborEventConsumer c) {
|
||||
return mIpNeighborMonitor;
|
||||
}
|
||||
|
||||
public void setOnDhcpServerCreatedResult(final int result) {
|
||||
mOnDhcpServerCreatedResult = result;
|
||||
}
|
||||
}
|
||||
|
||||
public class MockTetheringDependencies extends TetheringDependencies {
|
||||
|
|
@ -708,6 +718,7 @@ public class TetheringTest {
|
|||
|
||||
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 @@ public class TetheringTest {
|
|||
|
||||
@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 @@ public class TetheringTest {
|
|||
// 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 @@ public class TetheringTest {
|
|||
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 @@ public class TetheringTest {
|
|||
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 @@ public class TetheringTest {
|
|||
}
|
||||
|
||||
@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 @@ public class TetheringTest {
|
|||
@Test
|
||||
public void testStartBluetoothTetheringFailsWhenTheresAnExistingRequestWaitingForPanService()
|
||||
throws Exception {
|
||||
assumeFalse(isTetheringWithSoftApConfigEnabled());
|
||||
initTetheringOnTestThread();
|
||||
|
||||
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import static android.net.TetheringManager.TETHERING_USB;
|
|||
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 @@ public class TetheringManagerTest {
|
|||
|
||||
}
|
||||
|
||||
@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 @@ public class TetheringManagerTest {
|
|||
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));
|
||||
|
|
|
|||
Loading…
Reference in a new issue