Merge changes Ia8a8eab7,I1e193f35 into main am: a4b6164304 am: 4f521b4bdf

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/3527440

Change-Id: Ife6167630674c0ab4e6e3657227054a6419d05b2
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Quang Luong 2025-03-13 00:17:51 -07:00 • committed by Automerger Merge Worker
commit 18457dafbb
7 changed files with 886 additions and 137 deletions

View file

@ -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)

View file

@ -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.

View file

@ -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,83 @@ 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);
}
/**
* Returns an existing (pending or serving) request that fuzzy matches the given request.
* Optionally specify matchUid to only return requests with the same uid.
*/
public TetheringRequest findFuzzyMatchedRequest(
@NonNull final TetheringRequest tetheringRequest, boolean matchUid) {
List<TetheringRequest> allRequests = new ArrayList<>();
allRequests.addAll(getPendingTetheringRequests());
allRequests.addAll(mServingRequests.values());
for (TetheringRequest request : allRequests) {
if (!request.fuzzyMatches(tetheringRequest)) continue;
if (matchUid && tetheringRequest.getUid() != request.getUid()) continue;
return request;
}
return null;
}
}

View file

@ -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()) {
@ -726,39 +737,28 @@ public class Tethering {
});
}
private boolean isTetheringTypePendingOrServing(final int type) {
if (mRequestTracker.getNextPendingRequest(type) != null) return true;
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.
if (state.isCurrentlyServing() && state.ipServer.interfaceType() == type) return true;
}
return false;
}
void stopTetheringRequest(@NonNull final TetheringRequest request,
@NonNull final IIntResultListener listener) {
if (!isTetheringWithSoftApConfigEnabled()) return;
final boolean hasNetworkSettings = hasCallingPermission(NETWORK_SETTINGS);
mHandler.post(() -> {
final int type = request.getTetheringType();
if (isTetheringTypePendingOrServing(type)) {
if (mRequestTracker.findFuzzyMatchedRequest(request, !hasNetworkSettings) != null) {
final int type = request.getTetheringType();
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 +802,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 +866,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 +907,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 +924,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 +1101,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 +1138,7 @@ public class Tethering {
// Do nothing
break;
}
try {
listener.onResult(result);
} catch (RemoteException e) { }
sendTetherResult(listener, result);
}
/**
@ -1167,11 +1171,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 +1193,7 @@ public class Tethering {
return;
}
mHandler.post(() -> {
try {
listener.onResult(legacyUntetherInternal(iface));
} catch (RemoteException e) {
}
sendTetherResult(listener, legacyUntetherInternal(iface));
});
}
@ -1209,7 +1208,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 +1288,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 +1615,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 +1632,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 +1779,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 +2906,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 +2923,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 +3031,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 +3050,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);
});
}

View file

@ -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);
}
}

View file

@ -50,15 +50,18 @@ 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;
import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_REQUEST;
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 +96,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 +201,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 +313,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 +400,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 +444,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 +455,10 @@ public class TetheringTest {
IpNeighborMonitor.NeighborEventConsumer c) {
return mIpNeighborMonitor;
}
public void setOnDhcpServerCreatedResult(final int result) {
mOnDhcpServerCreatedResult = result;
}
}
public class MockTetheringDependencies extends TetheringDependencies {
@ -708,6 +719,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 +2437,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 +2494,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 +2517,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 +2533,332 @@ 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 testStopTetheringWithMatchingRequest() throws Exception {
assumeTrue(isTetheringWithSoftApConfigEnabled());
when(mContext.checkCallingOrSelfPermission(NETWORK_SETTINGS)).thenReturn(PERMISSION_DENIED);
initTetheringOnTestThread();
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
initTetheringUpstream(upstreamState);
when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
// Enable wifi tethering.
SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
.setSsid("SoftApConfig")
.build();
final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
.setSoftApConfiguration(softApConfig).build();
tetheringRequest.setUid(TEST_CALLER_UID);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
// Stop tethering with non-matching config. Should fail with TETHER_ERROR_UNKNOWN_REQUEST.
SoftApConfiguration softApConfig2 = new SoftApConfiguration.Builder()
.setSsid("SoftApConfig2")
.build();
IIntResultListener differentConfigListener = mock(IIntResultListener.class);
mTethering.stopTetheringRequest(new TetheringRequest.Builder(TETHERING_WIFI)
.setSoftApConfiguration(softApConfig2).build(), differentConfigListener);
mLooper.dispatchAll();
verify(differentConfigListener).onResult(eq(TETHER_ERROR_UNKNOWN_REQUEST));
verify(mWifiManager, never()).stopSoftAp();
// Stop tethering with non-matching UID. Should fail with TETHER_ERROR_UNKNOWN_REQUEST.
final TetheringRequest nonMatchingUid = new TetheringRequest.Builder(TETHERING_WIFI)
.setSoftApConfiguration(softApConfig).build();
IIntResultListener nonMatchingUidListener = mock(IIntResultListener.class);
nonMatchingUid.setUid(TEST_CALLER_UID_2);
mTethering.stopTetheringRequest(nonMatchingUid, nonMatchingUidListener);
mLooper.dispatchAll();
verify(nonMatchingUidListener).onResult(eq(TETHER_ERROR_UNKNOWN_REQUEST));
verify(mWifiManager, never()).stopSoftAp();
// Stop tethering with matching request. Should succeed now.
IIntResultListener matchingListener = mock(IIntResultListener.class);
mTethering.stopTetheringRequest(tetheringRequest, matchingListener);
mLooper.dispatchAll();
verify(matchingListener).onResult(eq(TETHER_ERROR_NO_ERROR));
verify(mWifiManager).stopSoftAp();
}
@Test
public void testStopTetheringWithSettingsPermission() throws Exception {
assumeTrue(isTetheringWithSoftApConfigEnabled());
initTetheringOnTestThread();
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
initTetheringUpstream(upstreamState);
when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
// Enable wifi tethering.
SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
.setSsid("SoftApConfig")
.build();
final TetheringRequest tetheringRequest = new TetheringRequest.Builder(TETHERING_WIFI)
.setSoftApConfiguration(softApConfig).build();
tetheringRequest.setUid(TEST_CALLER_UID);
mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
// Stop tethering with non-matching UID and Settings permission. Should succeed.
final TetheringRequest nonMatchingUid = new TetheringRequest.Builder(TETHERING_WIFI)
.setSoftApConfiguration(softApConfig).build();
IIntResultListener nonMatchingUidListener = mock(IIntResultListener.class);
nonMatchingUid.setUid(TEST_CALLER_UID_2);
when(mContext.checkCallingOrSelfPermission(NETWORK_SETTINGS))
.thenReturn(PERMISSION_GRANTED);
mTethering.stopTetheringRequest(nonMatchingUid, nonMatchingUidListener);
mLooper.dispatchAll();
verify(nonMatchingUidListener).onResult(eq(TETHER_ERROR_NO_ERROR));
verify(mWifiManager).stopSoftAp();
}
@Test
public void testReportFailCallbackIfOffloadNotSupported() throws Exception {
initTetheringOnTestThread();
@ -2794,7 +3125,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 +3907,7 @@ public class TetheringTest {
@Test
public void testStartBluetoothTetheringFailsWhenTheresAnExistingRequestWaitingForPanService()
throws Exception {
assumeFalse(isTetheringWithSoftApConfigEnabled());
initTetheringOnTestThread();
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);

View file

@ -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));
@ -455,25 +483,73 @@ public class TetheringManagerTest {
}
@Test
public void testStopTetheringRequest() throws Exception {
public void testStopTetheringRequestNoMatchFailure() throws Exception {
assumeTrue(isTetheringWithSoftApConfigEnabled());
final TestTetheringEventCallback tetherEventCallback =
mCtsTetheringUtils.registerTetheringEventCallback();
try {
final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
mTM.startTethering(new TetheringRequest.Builder(TETHERING_VIRTUAL).build(),
c -> c.run(), startTetheringCallback);
// Stopping a non-matching request should have no effect
TetheringRequest nonMatchingRequest = new TetheringRequest.Builder(TETHERING_VIRTUAL)
.setInterfaceName("iface")
.build();
mCtsTetheringUtils.stopTethering(nonMatchingRequest, false /* success */);
} finally {
mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
}
}
@Test
public void testStopTetheringRequestMatchSuccess() throws Exception {
assumeTrue(isTetheringWithSoftApConfigEnabled());
final TestTetheringEventCallback tetherEventCallback =
mCtsTetheringUtils.registerTetheringEventCallback();
try {
tetherEventCallback.assumeWifiTetheringSupported(mContext);
// stopTethering without any tethering active should fail.
TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
mCtsTetheringUtils.stopTethering(request, false /* expectSuccess */);
SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
.setWifiSsid(WifiSsid.fromBytes("This is one config"
.getBytes(StandardCharsets.UTF_8))).build();
mCtsTetheringUtils.startWifiTethering(tetherEventCallback, softApConfig);
// Start wifi tethering
mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
// Stopping the active request should stop tethering.
TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
.setSoftApConfiguration(softApConfig)
.build();
mCtsTetheringUtils.stopTethering(request, true /* success */);
tetherEventCallback.expectNoTetheringActive();
} finally {
mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
}
}
// stopTethering should succeed now that there's a request.
mCtsTetheringUtils.stopTethering(request, true /* expectSuccess */);
@Test
public void testStopTetheringRequestFuzzyMatchSuccess() throws Exception {
assumeTrue(isTetheringWithSoftApConfigEnabled());
final TestTetheringEventCallback tetherEventCallback =
mCtsTetheringUtils.registerTetheringEventCallback();
try {
tetherEventCallback.assumeWifiTetheringSupported(mContext);
SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
.setWifiSsid(WifiSsid.fromBytes("This is one config"
.getBytes(StandardCharsets.UTF_8))).build();
mCtsTetheringUtils.startWifiTethering(tetherEventCallback, softApConfig);
// Stopping with a fuzzy matching request should stop tethering.
final LinkAddress localAddr = new LinkAddress("192.168.24.5/24");
final LinkAddress clientAddr = new LinkAddress("192.168.24.100/24");
TetheringRequest fuzzyMatchingRequest = new TetheringRequest.Builder(TETHERING_WIFI)
.setSoftApConfiguration(softApConfig)
.setShouldShowEntitlementUi(true)
.setStaticIpv4Addresses(localAddr, clientAddr)
.build();
mCtsTetheringUtils.stopTethering(fuzzyMatchingRequest, true /* success */);
tetherEventCallback.expectNoTetheringActive();
} finally {
mCtsTetheringUtils.stopAllTethering();
mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
}
}