Merge "no-op: Use helper class to handle pending requests" into main
diff --git a/Tethering/src/com/android/networkstack/tethering/RequestTracker.java b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
new file mode 100644
index 0000000..3ebe4f7
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/RequestTracker.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering;
+
+import static com.android.networkstack.tethering.util.TetheringUtils.createPlaceholderRequest;
+
+import android.net.TetheringManager.TetheringRequest;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to keep track of tethering requests.
+ * The intended usage of this class is
+ * 1) Add a pending request with {@link #addPendingRequest(TetheringRequest)} before asking the link
+ * 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)}.
+ * 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;
+
+ private PendingRequest(@NonNull TetheringRequest tetheringRequest) {
+ mTetheringRequest = tetheringRequest;
+ }
+
+ @NonNull
+ TetheringRequest getTetheringRequest() {
+ return mTetheringRequest;
+ }
+ }
+
+ public enum AddResult {
+ /**
+ * Request was successfully added
+ */
+ SUCCESS,
+ /**
+ * 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.
+ */
+ FAILURE_CONFLICTING_PENDING_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<>();
+
+ @VisibleForTesting
+ List<TetheringRequest> getPendingTetheringRequests() {
+ List<TetheringRequest> requests = new ArrayList<>();
+ for (PendingRequest pendingRequest : mPendingRequests) {
+ requests.add(pendingRequest.getTetheringRequest());
+ }
+ return requests;
+ }
+
+ /**
+ * Add a pending request and listener. The request should be added before asking the link layer
+ * to start, and should be retrieved with {@link #getNextPendingRequest(int)} once the link
+ * layer comes up. The result of the add operation will be returned as an AddResult code.
+ */
+ public AddResult addPendingRequest(@NonNull final TetheringRequest newRequest) {
+ // Check the existing requests to see if it is OK to add the new request.
+ for (PendingRequest request : mPendingRequests) {
+ TetheringRequest existingRequest = request.getTetheringRequest();
+ if (existingRequest.getTetheringType() != newRequest.getTetheringType()) {
+ continue;
+ }
+
+ // 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;
+ }
+ }
+
+ // Remove the existing pending request of the same type. We already filter out for
+ // conflicting parameters above, so these would have been equivalent anyway (except for
+ // UID).
+ removeAllPendingRequests(newRequest.getTetheringType());
+ mPendingRequests.add(new PendingRequest(newRequest));
+ return AddResult.SUCCESS;
+ }
+
+ /**
+ * Gets the next pending TetheringRequest of a given type, or creates a placeholder request if
+ * there are none.
+ * Note: There are edge cases where the pending request is absent and we must temporarily
+ * synthesize a placeholder request, such as if stopTethering was called before link
+ * layer went up, or if the link layer goes up without us poking it (e.g. adb shell
+ * cmd wifi start-softap). These placeholder requests only specify the tethering type
+ * and the default connectivity scope.
+ */
+ @NonNull
+ public TetheringRequest getOrCreatePendingRequest(int type) {
+ TetheringRequest pending = getNextPendingRequest(type);
+ if (pending != null) return pending;
+
+ Log.w(TAG, "No pending TetheringRequest for type " + type + " found, creating a"
+ + " placeholder request");
+ return createPlaceholderRequest(type);
+ }
+
+ /**
+ * Same as {@link #getOrCreatePendingRequest(int)} but returns {@code null} if there's no
+ * pending request found.
+ *
+ * @param type Tethering type of the pending request
+ * @return pending request or {@code null} if there are none.
+ */
+ @Nullable
+ public TetheringRequest getNextPendingRequest(int type) {
+ for (PendingRequest pendingRequest : mPendingRequests) {
+ TetheringRequest tetheringRequest =
+ pendingRequest.getTetheringRequest();
+ if (tetheringRequest.getTetheringType() == type) return tetheringRequest;
+ }
+ return null;
+ }
+
+ /**
+ * Removes all pending requests of the given tethering type.
+ *
+ * @param type Tethering type
+ */
+ public void removeAllPendingRequests(int type) {
+ mPendingRequests.removeIf(r -> r.getTetheringRequest().getTetheringType() == type);
+ }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index e37c5db..1589509 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -29,7 +29,6 @@
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
-import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
@@ -78,6 +77,9 @@
import static com.android.networkstack.tethering.metrics.TetheringStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_LEGACY_TETHER_WITH_TYPE_WIFI_SUCCESS;
import static com.android.networkstack.tethering.metrics.TetheringStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_TETHER_WITH_PLACEHOLDER_REQUEST;
import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_MAIN_SM;
+import static com.android.networkstack.tethering.util.TetheringUtils.createImplicitLocalOnlyTetheringRequest;
+import static com.android.networkstack.tethering.util.TetheringUtils.createLegacyGlobalScopeTetheringRequest;
+import static com.android.networkstack.tethering.util.TetheringUtils.createPlaceholderRequest;
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
@@ -241,11 +243,7 @@
private final SharedLog mLog = new SharedLog(TAG);
private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
new RemoteCallbackList<>();
- // Currently active tethering requests per tethering type. Only one of each type can be
- // requested at a time. After a tethering type is requested, the map keeps tethering parameters
- // to be used after the interface comes up asynchronously.
- private final SparseArray<TetheringRequest> mPendingTetheringRequests =
- new SparseArray<>();
+ private final RequestTracker mRequestTracker;
private final Context mContext;
private final ArrayMap<String, TetherState> mTetherStates;
@@ -308,6 +306,7 @@
mLooper = mDeps.makeTetheringLooper();
mNotificationUpdater = mDeps.makeNotificationUpdater(mContext, mLooper);
mTetheringMetrics = mDeps.makeTetheringMetrics(mContext);
+ mRequestTracker = new RequestTracker();
// This is intended to ensrure that if something calls startTethering(bluetooth) just after
// bluetooth is enabled. Before onServiceConnected is called, store the calls into this
@@ -704,13 +703,13 @@
final IIntResultListener listener) {
mHandler.post(() -> {
final int type = request.getTetheringType();
- final TetheringRequest unfinishedRequest = mPendingTetheringRequests.get(type);
- // If tethering is already enabled with a different request,
- // disable before re-enabling.
- if (unfinishedRequest != null && !unfinishedRequest.equalsIgnoreUidPackage(request)) {
- stopTetheringInternal(type);
+ 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) {
+ stopTetheringInternal(type); // Also removes the request from the tracker.
+ mRequestTracker.addPendingRequest(request);
}
- mPendingTetheringRequests.put(type, request);
if (request.isExemptFromEntitlementCheck()) {
mEntitlementMgr.setExemptedDownstreamType(type);
@@ -730,9 +729,7 @@
}
private boolean isTetheringTypePendingOrServing(final int type) {
- for (int i = 0; i < mPendingTetheringRequests.size(); i++) {
- if (mPendingTetheringRequests.valueAt(i).getTetheringType() == type) return true;
- }
+ 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
@@ -763,7 +760,7 @@
}
void stopTetheringInternal(int type) {
- mPendingTetheringRequests.remove(type);
+ mRequestTracker.removeAllPendingRequests(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.
@@ -821,7 +818,7 @@
// If changing tethering fail, remove corresponding request
// no matter who trigger the start/stop.
if (result != TETHER_ERROR_NO_ERROR) {
- mPendingTetheringRequests.remove(type);
+ mRequestTracker.removeAllPendingRequests(type);
mTetheringMetrics.updateErrorCode(type, result);
mTetheringMetrics.sendReport(type);
}
@@ -990,7 +987,7 @@
if (this != mBluetoothCallback) return;
final TetheringRequest request =
- getOrCreatePendingTetheringRequest(TETHERING_BLUETOOTH);
+ mRequestTracker.getOrCreatePendingRequest(TETHERING_BLUETOOTH);
enableIpServing(request, iface);
mConfiguredBluetoothIface = iface;
}
@@ -1047,7 +1044,8 @@
return;
}
- final TetheringRequest request = getOrCreatePendingTetheringRequest(TETHERING_ETHERNET);
+ final TetheringRequest request = mRequestTracker.getOrCreatePendingRequest(
+ TETHERING_ETHERNET);
enableIpServing(request, iface);
mConfiguredEthernetIface = iface;
}
@@ -1079,61 +1077,6 @@
return TETHER_ERROR_NO_ERROR;
}
- /**
- * Create a legacy tethering request for calls to the legacy tether() API, which doesn't take an
- * explicit request. These are always CONNECTIVITY_SCOPE_GLOBAL, per historical behavior.
- */
- private TetheringRequest createLegacyGlobalScopeTetheringRequest(int type) {
- final TetheringRequest request = new TetheringRequest.Builder(type).build();
- request.getParcel().requestType = TetheringRequest.REQUEST_TYPE_LEGACY;
- request.getParcel().connectivityScope = CONNECTIVITY_SCOPE_GLOBAL;
- return request;
- }
-
- /**
- * Create a local-only implicit tethering request. This is used for Wifi local-only hotspot and
- * Wifi P2P, which start tethering based on the WIFI_(AP/P2P)_STATE_CHANGED broadcasts.
- */
- @NonNull
- private TetheringRequest createImplicitLocalOnlyTetheringRequest(int type) {
- final TetheringRequest request = new TetheringRequest.Builder(type).build();
- request.getParcel().requestType = TetheringRequest.REQUEST_TYPE_IMPLICIT;
- request.getParcel().connectivityScope = CONNECTIVITY_SCOPE_LOCAL;
- return request;
- }
-
- /**
- * Create a placeholder request. This is used in case we try to find a pending request but there
- * is none (e.g. stopTethering removed a pending request), or for cases where we only have the
- * tethering type (e.g. stopTethering(int)).
- */
- @NonNull
- private TetheringRequest createPlaceholderRequest(int type) {
- final TetheringRequest request = new TetheringRequest.Builder(type).build();
- request.getParcel().requestType = TetheringRequest.REQUEST_TYPE_LEGACY;
- request.getParcel().connectivityScope = CONNECTIVITY_SCOPE_GLOBAL;
- return request;
- }
-
- /**
- * Gets the TetheringRequest that #startTethering was called with but is waiting for the link
- * layer event to indicate the interface is available to tether.
- * Note: There are edge cases where the pending request is absent and we must temporarily
- * synthesize a placeholder request, such as if stopTethering was called before link layer
- * went up, or if the link layer goes up without us poking it (e.g. adb shell cmd wifi
- * start-softap). These placeholder requests only specify the tethering type and the
- * default connectivity scope.
- */
- @NonNull
- private TetheringRequest getOrCreatePendingTetheringRequest(int type) {
- TetheringRequest pending = mPendingTetheringRequests.get(type);
- if (pending != null) return pending;
-
- Log.w(TAG, "No pending TetheringRequest for type " + type + " found, creating a placeholder"
- + " request");
- return createPlaceholderRequest(type);
- }
-
private void handleLegacyTether(String iface, final IIntResultListener listener) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
// After V, the TetheringManager and ConnectivityManager tether and untether methods
@@ -1151,7 +1094,7 @@
} catch (RemoteException e) { }
}
- TetheringRequest request = getPendingTetheringRequest(type);
+ TetheringRequest request = mRequestTracker.getNextPendingRequest(type);
if (request == null) {
request = createLegacyGlobalScopeTetheringRequest(type);
}
@@ -1224,7 +1167,7 @@
// 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.
- mPendingTetheringRequests.remove(request.getTetheringType());
+ mRequestTracker.removeAllPendingRequests(request.getTetheringType());
tetherState.ipServer.enable(request);
if (request.getRequestType() == REQUEST_TYPE_PLACEHOLDER) {
TerribleErrorLog.logTerribleError(TetheringStatsLog::write,
@@ -1590,8 +1533,8 @@
}
@VisibleForTesting
- SparseArray<TetheringRequest> getPendingTetheringRequests() {
- return mPendingTetheringRequests;
+ List<TetheringRequest> getPendingTetheringRequests() {
+ return mRequestTracker.getPendingTetheringRequests();
}
@VisibleForTesting
@@ -1651,10 +1594,6 @@
}
}
- final TetheringRequest getPendingTetheringRequest(int type) {
- return mPendingTetheringRequests.get(type, null);
- }
-
private void enableIpServing(@NonNull TetheringRequest request, String ifname) {
enableIpServing(request, ifname, false /* isNcm */);
}
@@ -1742,7 +1681,7 @@
switch (wifiIpMode) {
case IFACE_IP_MODE_TETHERED:
type = maybeInferWifiTetheringType(ifname);
- request = getOrCreatePendingTetheringRequest(type);
+ request = mRequestTracker.getOrCreatePendingRequest(type);
// Wifi requests will always have CONNECTIVITY_SCOPE_GLOBAL, because
// TetheringRequest.Builder will not allow callers to set CONNECTIVITY_SCOPE_LOCAL
// for TETHERING_WIFI. However, if maybeInferWifiTetheringType returns a non-Wifi
@@ -1797,7 +1736,7 @@
return;
}
- final TetheringRequest request = getOrCreatePendingTetheringRequest(tetheringType);
+ final TetheringRequest request = mRequestTracker.getOrCreatePendingRequest(tetheringType);
if (ifaces != null) {
for (String iface : ifaces) {
if (ifaceNameToType(iface) == tetheringType) {
diff --git a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
index 76c2f0d..79e6e16 100644
--- a/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
+++ b/Tethering/src/com/android/networkstack/tethering/util/TetheringUtils.java
@@ -15,7 +15,11 @@
*/
package com.android.networkstack.tethering.util;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
+
import android.net.TetherStatsParcel;
+import android.net.TetheringManager.TetheringRequest;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -166,4 +170,41 @@
return null;
}
}
+
+ /**
+ * Create a legacy tethering request for calls to the legacy tether() API, which doesn't take an
+ * explicit request. These are always CONNECTIVITY_SCOPE_GLOBAL, per historical behavior.
+ */
+ @NonNull
+ public static TetheringRequest createLegacyGlobalScopeTetheringRequest(int type) {
+ final TetheringRequest request = new TetheringRequest.Builder(type).build();
+ request.getParcel().requestType = TetheringRequest.REQUEST_TYPE_LEGACY;
+ request.getParcel().connectivityScope = CONNECTIVITY_SCOPE_GLOBAL;
+ return request;
+ }
+
+ /**
+ * Create a local-only implicit tethering request. This is used for Wifi local-only hotspot and
+ * Wifi P2P, which start tethering based on the WIFI_(AP/P2P)_STATE_CHANGED broadcasts.
+ */
+ @NonNull
+ public static TetheringRequest createImplicitLocalOnlyTetheringRequest(int type) {
+ final TetheringRequest request = new TetheringRequest.Builder(type).build();
+ request.getParcel().requestType = TetheringRequest.REQUEST_TYPE_IMPLICIT;
+ request.getParcel().connectivityScope = CONNECTIVITY_SCOPE_LOCAL;
+ return request;
+ }
+
+ /**
+ * Create a placeholder request. This is used in case we try to find a pending request but there
+ * is none (e.g. stopTethering removed a pending request), or for cases where we only have the
+ * tethering type (e.g. stopTethering(int)).
+ */
+ @NonNull
+ public static TetheringRequest createPlaceholderRequest(int type) {
+ final TetheringRequest request = new TetheringRequest.Builder(type).build();
+ request.getParcel().requestType = TetheringRequest.REQUEST_TYPE_PLACEHOLDER;
+ request.getParcel().connectivityScope = CONNECTIVITY_SCOPE_GLOBAL;
+ return request;
+ }
}
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index d0d23ac..ee82776 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -57,6 +57,7 @@
"mockito-target-extended-minus-junit4",
"net-tests-utils",
"testables",
+ "truth",
],
// TODO(b/147200698) change sdk_version to module-current and
// remove framework-minus-apex, ext, and framework-res
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/RequestTrackerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/RequestTrackerTest.java
new file mode 100644
index 0000000..e00e9f0
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/RequestTrackerTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering;
+
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+
+import static com.android.networkstack.tethering.util.TetheringUtils.createPlaceholderRequest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.TetheringManager.TetheringRequest;
+
+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;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RequestTrackerTest {
+ private RequestTracker mRequestTracker;
+
+ @Before
+ public void setUp() {
+ mRequestTracker = new RequestTracker();
+ }
+
+ @Test
+ public void testNoRequestsAdded_noPendingRequests() {
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
+ .isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
+ }
+
+ @Test
+ public void testAddRequest_successResultAndBecomesNextPending() {
+ 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_equalRequestExists_successResultAndBecomesNextPending() {
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ mRequestTracker.addPendingRequest(request);
+
+ final TetheringRequest equalRequest = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ final AddResult result = mRequestTracker.addPendingRequest(equalRequest);
+
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ }
+
+ @Test
+ public void testAddRequest_equalButDifferentUidRequest_successResultAndBecomesNextPending() {
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ request.setUid(1000);
+ request.setPackageName("package");
+ final TetheringRequest differentUid = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ differentUid.setUid(2000);
+ differentUid.setPackageName("package2");
+ mRequestTracker.addPendingRequest(request);
+
+ final AddResult result = mRequestTracker.addPendingRequest(differentUid);
+
+ assertThat(result).isEqualTo(AddResult.SUCCESS);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(differentUid);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
+ .isEqualTo(differentUid);
+ }
+
+ @Test
+ public void testAddConflictingRequest_returnsFailureConflictingPendingRequest() {
+ 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_CONFLICTING_PENDING_REQUEST);
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI)).isEqualTo(request);
+ }
+
+ @Test
+ public void testRemoveAllPendingRequests_noPendingRequestsLeft() {
+ final TetheringRequest firstRequest = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ firstRequest.setUid(1000);
+ firstRequest.setPackageName("package");
+ mRequestTracker.addPendingRequest(firstRequest);
+ final TetheringRequest secondRequest = new TetheringRequest.Builder(TETHERING_WIFI).build();
+ secondRequest.setUid(2000);
+ secondRequest.setPackageName("package2");
+ mRequestTracker.addPendingRequest(secondRequest);
+
+ mRequestTracker.removeAllPendingRequests(TETHERING_WIFI);
+
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_WIFI)).isNull();
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_WIFI))
+ .isEqualTo(createPlaceholderRequest(TETHERING_WIFI));
+ }
+
+ @Test
+ public void testRemoveAllPendingRequests_differentTypeExists_doesNotRemoveDifferentType() {
+ final TetheringRequest differentType = new TetheringRequest.Builder(TETHERING_USB).build();
+ mRequestTracker.addPendingRequest(differentType);
+
+ mRequestTracker.removeAllPendingRequests(TETHERING_WIFI);
+
+ assertThat(mRequestTracker.getNextPendingRequest(TETHERING_USB)).isEqualTo(differentType);
+ assertThat(mRequestTracker.getOrCreatePendingRequest(TETHERING_USB))
+ .isEqualTo(differentType);
+ }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 50ecfe1..51efaf8 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -964,7 +964,7 @@
mLooper.dispatchAll();
assertEquals(1, mTethering.getPendingTetheringRequests().size());
- assertEquals(request, mTethering.getPendingTetheringRequests().get(TETHERING_USB));
+ assertTrue(mTethering.getPendingTetheringRequests().get(0).equals(request));
if (mTethering.getTetheringConfiguration().isUsingNcm()) {
verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NCM);