Merge "Add basic implementation of stopTetheringRequest" into main
diff --git a/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl b/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
index 77e78bd..7d244e2 100644
--- a/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
@@ -18,6 +18,7 @@
 import android.net.IIntResultListener;
 import android.net.ITetheringEventCallback;
 import android.net.TetheringRequestParcel;
+import android.net.TetheringManager.TetheringRequest;
 import android.os.ResultReceiver;
 
 /** @hide */
@@ -37,6 +38,9 @@
     void stopTethering(int type, String callerPkg, String callingAttributionTag,
             IIntResultListener receiver);
 
+    void stopTetheringRequest(in TetheringRequest request, String callerPkg,
+            String callingAttributionTag, IIntResultListener receiver);
+
     void requestLatestTetheringEntitlementResult(int type, in ResultReceiver receiver,
             boolean showEntitlementUi, String callerPkg, String callingAttributionTag);
 
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index aa7a244..f123dca 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -1417,7 +1417,25 @@
     @FlaggedApi(Flags.FLAG_TETHERING_WITH_SOFT_AP_CONFIG)
     public void stopTethering(@NonNull TetheringRequest request,
             @NonNull final Executor executor, @NonNull final StopTetheringCallback callback) {
-        throw new UnsupportedOperationException();
+        Objects.requireNonNull(request);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        final String callerPkg = mContext.getOpPackageName();
+        Log.i(TAG, "stopTethering: request=" + request + ", caller=" + callerPkg);
+        getConnector(c -> c.stopTetheringRequest(request, callerPkg, getAttributionTag(),
+                new IIntResultListener.Stub() {
+                    @Override
+                    public void onResult(final int resultCode) {
+                        executor.execute(() -> {
+                            if (resultCode == TETHER_ERROR_NO_ERROR) {
+                                callback.onStopTetheringSucceeded();
+                            } else {
+                                callback.onStopTetheringFailed(resultCode);
+                            }
+                        });
+                    }
+                }));
     }
 
     /**
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 21943b0..c2a7622 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -48,6 +48,7 @@
 import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
 import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
 import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_REQUEST;
 import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_TYPE;
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED;
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
@@ -729,6 +730,37 @@
             stopTetheringInternal(type);
         });
     }
+
+    private boolean isTetheringTypePendingOrServing(final int type) {
+        for (int i = 0; i < mPendingTetheringRequests.size(); i++) {
+            if (mPendingTetheringRequests.valueAt(i).getTetheringType() == type) return true;
+        }
+        for (TetherState state : mTetherStates.values()) {
+            if (state.isCurrentlyServing() && state.ipServer.interfaceType() == type) return true;
+        }
+        return false;
+    }
+
+    void stopTetheringRequest(@NonNull final TetheringRequest request,
+            @NonNull final IIntResultListener listener) {
+        if (!isTetheringWithSoftApConfigEnabled()) return;
+        mHandler.post(() -> {
+            final int type = request.getTetheringType();
+            if (isTetheringTypePendingOrServing(type)) {
+                stopTetheringInternal(type);
+                try {
+                    listener.onResult(TETHER_ERROR_NO_ERROR);
+                } catch (RemoteException ignored) { }
+                return;
+            }
+
+            // Request doesn't match any active requests, ignore.
+            try {
+                listener.onResult(TETHER_ERROR_UNKNOWN_REQUEST);
+            } catch (RemoteException ignored) { }
+        });
+    }
+
     void stopTetheringInternal(int type) {
         mPendingTetheringRequests.remove(type);
 
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 4f00332..b553f46 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -159,6 +159,18 @@
         }
 
         @Override
+        public void stopTetheringRequest(TetheringRequest request,
+                String callerPkg, String callingAttributionTag,
+                IIntResultListener listener) {
+            if (request == null) return;
+            if (listener == null) return;
+            if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+            request.setUid(getBinderCallingUid());
+            request.setPackageName(callerPkg);
+            mTethering.stopTetheringRequest(request, listener);
+        }
+
+        @Override
         public void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
                 boolean showEntitlementUi, String callerPkg, String callingAttributionTag) {
             if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, receiver)) return;
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
index 82a295d..75b2814 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
@@ -22,6 +22,7 @@
 import static android.Manifest.permission.TETHER_PRIVILEGED;
 import static android.net.TetheringManager.TETHERING_WIFI;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+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;
@@ -637,17 +638,16 @@
 
     /**
      * Calls {@link TetheringManager#stopTethering(TetheringRequest, Executor,
-     * TetheringManager.StopTetheringCallback)} and verifies it throws an
-     * UnsupportedOperationException.
+     * TetheringManager.StopTetheringCallback)} and verifies if it succeeded or failed.
      */
-    public void stopTethering(final TetheringRequest request) {
-        final StopTetheringCallback callback = new StopTetheringCallback();
+    public void stopTethering(final TetheringRequest request, boolean expectSuccess) {
+        final StopTetheringCallback stopTetheringCallback = new StopTetheringCallback();
         runAsShell(TETHER_PRIVILEGED, () -> {
-            try {
-                mTm.stopTethering(request, Runnable::run /* Executor */, callback);
-                fail("stopTethering should throw UnsupportedOperationException");
-            } catch (UnsupportedOperationException expected) {
-                // Success.
+            mTm.stopTethering(request, c -> c.run() /* executor */, stopTetheringCallback);
+            if (expectSuccess) {
+                stopTetheringCallback.verifyStopTetheringSucceeded();
+            } else {
+                stopTetheringCallback.expectStopTetheringFailed(TETHER_ERROR_UNKNOWN_REQUEST);
             }
         });
     }
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index a2cac69..7ce99eb 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -458,8 +458,25 @@
     @Test
     public void testStopTetheringRequest() throws Exception {
         assumeTrue(isTetheringWithSoftApConfigEnabled());
-        TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build();
-        mCtsTetheringUtils.stopTethering(request);
+        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 /* succeeded */);
+
+            // Start wifi tethering
+            mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
+
+            // stopTethering should succeed now that there's a request.
+            mCtsTetheringUtils.stopTethering(request, true /* succeeded */);
+            tetherEventCallback.expectNoTetheringActive();
+        } finally {
+            mCtsTetheringUtils.stopAllTethering();
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+        }
     }
 
     private boolean isTetheringWithSoftApConfigEnabled() {