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 0000000000..3ebe4f772a --- /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 mPendingRequests = new ArrayList<>(); + + @VisibleForTesting + List getPendingTetheringRequests() { + List 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 e37c5db55b..1589509ea9 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.ConnectivityManager.EXTRA_NETWORK_INFO; 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_ 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 @@ public class Tethering { private final SharedLog mLog = new SharedLog(TAG); private final RemoteCallbackList 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 mPendingTetheringRequests = - new SparseArray<>(); + private final RequestTracker mRequestTracker; private final Context mContext; private final ArrayMap mTetherStates; @@ -308,6 +306,7 @@ public class Tethering { 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 @@ public class Tethering { 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 @@ public class Tethering { } 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 @@ public class Tethering { } 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 @@ public class Tethering { // 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 @@ public class Tethering { if (this != mBluetoothCallback) return; final TetheringRequest request = - getOrCreatePendingTetheringRequest(TETHERING_BLUETOOTH); + mRequestTracker.getOrCreatePendingRequest(TETHERING_BLUETOOTH); enableIpServing(request, iface); mConfiguredBluetoothIface = iface; } @@ -1047,7 +1044,8 @@ public class Tethering { return; } - final TetheringRequest request = getOrCreatePendingTetheringRequest(TETHERING_ETHERNET); + final TetheringRequest request = mRequestTracker.getOrCreatePendingRequest( + TETHERING_ETHERNET); enableIpServing(request, iface); mConfiguredEthernetIface = iface; } @@ -1079,61 +1077,6 @@ public class Tethering { 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 @@ public class Tethering { } catch (RemoteException e) { } } - TetheringRequest request = getPendingTetheringRequest(type); + TetheringRequest request = mRequestTracker.getNextPendingRequest(type); if (request == null) { request = createLegacyGlobalScopeTetheringRequest(type); } @@ -1224,7 +1167,7 @@ public class Tethering { // 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 @@ public class Tethering { } @VisibleForTesting - SparseArray getPendingTetheringRequests() { - return mPendingTetheringRequests; + List getPendingTetheringRequests() { + return mRequestTracker.getPendingTetheringRequests(); } @VisibleForTesting @@ -1651,10 +1594,6 @@ public class Tethering { } } - 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 @@ public class Tethering { 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 @@ public class Tethering { 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 76c2f0d68b..79e6e16eec 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 @@ public class TetheringUtils { 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 d0d23ac28b..ee82776b9c 100644 --- a/Tethering/tests/unit/Android.bp +++ b/Tethering/tests/unit/Android.bp @@ -57,6 +57,7 @@ java_defaults { "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 0000000000..e00e9f001f --- /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 50ecfe1bfe..51efaf8746 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 @@ public class TetheringTest { 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);