Merge changes Ie2c35190,I6bd9ceeb,I823c8e94,I1b4868ba into main am: d9a752d866

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

Change-Id: I6c859205908ca5dd76b4f41e98b90aaff479b411
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index 1728e16..0e85956 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -21,8 +21,10 @@
 
   public final class TetheringInterface implements android.os.Parcelable {
     ctor public TetheringInterface(int, @NonNull String);
+    ctor @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") public TetheringInterface(int, @NonNull String, @Nullable android.net.wifi.SoftApConfiguration);
     method public int describeContents();
     method @NonNull public String getInterface();
+    method @FlaggedApi("com.android.net.flags.tethering_with_soft_ap_config") @Nullable @RequiresPermission(value=android.Manifest.permission.NETWORK_SETTINGS, conditional=true) public android.net.wifi.SoftApConfiguration getSoftApConfiguration();
     method public int getType();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheringInterface> CREATOR;
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
index 84cdef1..0464fe0 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
@@ -16,13 +16,19 @@
 
 package android.net;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.net.TetheringManager.TetheringType;
+import android.net.wifi.SoftApConfiguration;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.net.flags.Flags;
+
 import java.util.Objects;
 
 /**
@@ -33,15 +39,21 @@
 public final class TetheringInterface implements Parcelable {
     private final int mType;
     private final String mInterface;
+    @Nullable
+    private final SoftApConfiguration mSoftApConfig;
 
+    @SuppressLint("UnflaggedApi")
     public TetheringInterface(@TetheringType int type, @NonNull String iface) {
+        this(type, iface, null);
+    }
+
+    @FlaggedApi(Flags.FLAG_TETHERING_WITH_SOFT_AP_CONFIG)
+    public TetheringInterface(@TetheringType int type, @NonNull String iface,
+            @Nullable SoftApConfiguration softApConfig) {
         Objects.requireNonNull(iface);
         mType = type;
         mInterface = iface;
-    }
-
-    private TetheringInterface(@NonNull Parcel in) {
-        this(in.readInt(), in.readString());
+        mSoftApConfig = softApConfig;
     }
 
     /** Get tethering type. */
@@ -55,22 +67,36 @@
         return mInterface;
     }
 
+    /**
+     * Get the SoftApConfiguration provided for this interface, if any. This will only be populated
+     * for apps with the same uid that specified the configuration, or apps with permission
+     * {@link android.Manifest.permission.NETWORK_SETTINGS}.
+     */
+    @FlaggedApi(Flags.FLAG_TETHERING_WITH_SOFT_AP_CONFIG)
+    @RequiresPermission(value = android.Manifest.permission.NETWORK_SETTINGS, conditional = true)
+    @Nullable
+    public SoftApConfiguration getSoftApConfiguration() {
+        return mSoftApConfig;
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mType);
         dest.writeString(mInterface);
+        dest.writeParcelable(mSoftApConfig, flags);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mInterface);
+        return Objects.hash(mType, mInterface, mSoftApConfig);
     }
 
     @Override
     public boolean equals(@Nullable Object obj) {
         if (!(obj instanceof TetheringInterface)) return false;
         final TetheringInterface other = (TetheringInterface) obj;
-        return mType == other.mType && mInterface.equals(other.mInterface);
+        return mType == other.mType && mInterface.equals(other.mInterface)
+                && Objects.equals(mSoftApConfig, other.mSoftApConfig);
     }
 
     @Override
@@ -82,8 +108,10 @@
     public static final Creator<TetheringInterface> CREATOR = new Creator<TetheringInterface>() {
         @NonNull
         @Override
+        @SuppressLint("UnflaggedApi")
         public TetheringInterface createFromParcel(@NonNull Parcel in) {
-            return new TetheringInterface(in);
+            return new TetheringInterface(in.readInt(), in.readString(),
+                    in.readParcelable(SoftApConfiguration.class.getClassLoader()));
         }
 
         @NonNull
@@ -97,6 +125,8 @@
     @Override
     public String toString() {
         return "TetheringInterface {mType=" + mType
-                + ", mInterface=" + mInterface + "}";
+                + ", mInterface=" + mInterface
+                + ((mSoftApConfig == null) ? "" : ", mSoftApConfig=" + mSoftApConfig)
+                + "}";
     }
 }
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 6b96397..bc771da 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -54,6 +54,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.StringJoiner;
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
@@ -208,6 +209,20 @@
      */
     public static final int MAX_TETHERING_TYPE = TETHERING_VIRTUAL;
 
+    private static String typeToString(@TetheringType int type) {
+        switch (type) {
+            case TETHERING_INVALID: return "TETHERING_INVALID";
+            case TETHERING_WIFI: return "TETHERING_WIFI";
+            case TETHERING_USB: return "TETHERING_USB";
+            case TETHERING_BLUETOOTH: return "TETHERING_BLUETOOTH";
+            case TETHERING_WIFI_P2P: return "TETHERING_WIFI_P2P";
+            case TETHERING_NCM: return "TETHERING_NCM";
+            case TETHERING_ETHERNET: return "TETHERING_ETHERNET";
+            default:
+                return "TETHERING_UNKNOWN(" + type + ")";
+        }
+    }
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
@@ -689,6 +704,17 @@
     })
     public @interface ConnectivityScope {}
 
+    private static String connectivityScopeToString(@ConnectivityScope int scope) {
+        switch (scope) {
+            case CONNECTIVITY_SCOPE_GLOBAL:
+                return "CONNECTIVITY_SCOPE_GLOBAL";
+            case CONNECTIVITY_SCOPE_LOCAL:
+                return "CONNECTIVITY_SCOPE_LOCAL";
+            default:
+                return "CONNECTIVITY_SCOPE_UNKNOWN(" + scope + ")";
+        }
+    }
+
     /**
      *  Use with {@link #startTethering} to specify additional parameters when starting tethering.
      */
@@ -972,15 +998,31 @@
 
         /** String of TetheringRequest detail. */
         public String toString() {
-            return "TetheringRequest [ type= " + mRequestParcel.tetheringType
-                    + ", localIPv4Address= " + mRequestParcel.localIPv4Address
-                    + ", staticClientAddress= " + mRequestParcel.staticClientAddress
-                    + ", exemptFromEntitlementCheck= " + mRequestParcel.exemptFromEntitlementCheck
-                    + ", showProvisioningUi= " + mRequestParcel.showProvisioningUi
-                    + ", softApConfig= " + mRequestParcel.softApConfig
-                    + ", uid= " + mRequestParcel.uid
-                    + ", packageName= " + mRequestParcel.packageName
-                    + " ]";
+            StringJoiner sj = new StringJoiner(", ", "TetheringRequest[ ", " ]");
+            sj.add(typeToString(mRequestParcel.tetheringType));
+            if (mRequestParcel.localIPv4Address != null) {
+                sj.add("localIpv4Address=" + mRequestParcel.localIPv4Address);
+            }
+            if (mRequestParcel.staticClientAddress != null) {
+                sj.add("staticClientAddress=" + mRequestParcel.staticClientAddress);
+            }
+            if (mRequestParcel.exemptFromEntitlementCheck) {
+                sj.add("exemptFromEntitlementCheck");
+            }
+            if (mRequestParcel.showProvisioningUi) {
+                sj.add("showProvisioningUi");
+            }
+            sj.add(connectivityScopeToString(mRequestParcel.connectivityScope));
+            if (mRequestParcel.softApConfig != null) {
+                sj.add("softApConfig=" + mRequestParcel.softApConfig);
+            }
+            if (mRequestParcel.uid != Process.INVALID_UID) {
+                sj.add("uid=" + mRequestParcel.uid);
+            }
+            if (mRequestParcel.packageName != null) {
+                sj.add("packageName=" + mRequestParcel.packageName);
+            }
+            return sj.toString();
         }
 
         @Override
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index a0604f2..ebc9e4e 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -69,8 +69,8 @@
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.State;
 import com.android.modules.utils.build.SdkLevel;
-import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.IIpv4PrefixRequest;
+import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.RoutingCoordinatorManager;
 import com.android.net.module.util.SharedLog;
@@ -168,10 +168,10 @@
         /**
          * Request Tethering change.
          *
-         * @param tetheringType the downstream type of this IpServer.
+         * @param request the TetheringRequest this IpServer was enabled with.
          * @param enabled enable or disable tethering.
          */
-        public void requestEnableTethering(int tetheringType, boolean enabled) { }
+        public void requestEnableTethering(TetheringRequest request, boolean enabled) { }
     }
 
     /** Capture IpServer dependencies, for injection. */
@@ -293,6 +293,9 @@
 
     private LinkAddress mIpv4Address;
 
+    @Nullable
+    private TetheringRequest mTetheringRequest;
+
     private final TetheringMetrics mTetheringMetrics;
     private final Handler mHandler;
 
@@ -406,6 +409,12 @@
         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.
@@ -1033,6 +1042,7 @@
             switch (message.what) {
                 case CMD_TETHER_REQUESTED:
                     mLastError = TETHER_ERROR_NO_ERROR;
+                    mTetheringRequest = (TetheringRequest) message.obj;
                     switch (message.arg1) {
                         case STATE_LOCAL_ONLY:
                             maybeConfigureStaticIp((TetheringRequest) message.obj);
@@ -1168,8 +1178,8 @@
                     handleNewPrefixRequest((IpPrefix) message.obj);
                     break;
                 case CMD_NOTIFY_PREFIX_CONFLICT:
-                    mLog.i("restart tethering: " + mInterfaceType);
-                    mCallback.requestEnableTethering(mInterfaceType, false /* enabled */);
+                    mLog.i("restart tethering: " + mIfaceName);
+                    mCallback.requestEnableTethering(mTetheringRequest, false /* enabled */);
                     transitionTo(mWaitingForRestartState);
                     break;
                 case CMD_SERVICE_FAILED_TO_START:
@@ -1453,12 +1463,12 @@
                 case CMD_TETHER_UNREQUESTED:
                     transitionTo(mInitialState);
                     mLog.i("Untethered (unrequested) and restarting " + mIfaceName);
-                    mCallback.requestEnableTethering(mInterfaceType, true /* enabled */);
+                    mCallback.requestEnableTethering(mTetheringRequest, true /* enabled */);
                     break;
                 case CMD_INTERFACE_DOWN:
                     transitionTo(mUnavailableState);
                     mLog.i("Untethered (interface down) and restarting " + mIfaceName);
-                    mCallback.requestEnableTethering(mInterfaceType, true /* enabled */);
+                    mCallback.requestEnableTethering(mTetheringRequest, true /* enabled */);
                     break;
                 default:
                     return false;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 61833c2..f33ef37 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -109,6 +109,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -216,9 +217,11 @@
      * Cookie added when registering {@link android.net.TetheringManager.TetheringEventCallback}.
      */
     private static class CallbackCookie {
+        public final int uid;
         public final boolean hasSystemPrivilege;
 
-        private CallbackCookie(boolean hasSystemPrivilege) {
+        private CallbackCookie(int uid, boolean hasSystemPrivilege) {
+            this.uid = uid;
             this.hasSystemPrivilege = hasSystemPrivilege;
         }
     }
@@ -1116,7 +1119,9 @@
     }
 
     /**
-     * Builds a TetherStatesParcel for the specified CallbackCookie.
+     * Builds a TetherStatesParcel for the specified CallbackCookie. SoftApConfiguration will only
+     * be included if the cookie has the same uid as the app that specified the configuration, or
+     * if the cookie has system privilege.
      *
      * @param cookie CallbackCookie of the receiving app.
      * @return TetherStatesParcel with information redacted for the specified cookie.
@@ -1132,7 +1137,11 @@
             final TetherState tetherState = mTetherStates.valueAt(i);
             final int type = tetherState.ipServer.interfaceType();
             final String iface = mTetherStates.keyAt(i);
-            final TetheringInterface tetheringIface = new TetheringInterface(type, iface);
+            final TetheringRequest request = tetherState.ipServer.getTetheringRequest();
+            final boolean includeSoftApConfig = request != null && cookie != null
+                    && (cookie.uid == request.getUid() || cookie.hasSystemPrivilege);
+            final TetheringInterface tetheringIface = new TetheringInterface(type, iface,
+                    includeSoftApConfig ? request.getSoftApConfiguration() : null);
             if (tetherState.lastError != TETHER_ERROR_NO_ERROR) {
                 errored.add(tetheringIface);
                 lastErrors.add(tetherState.lastError);
@@ -1170,7 +1179,7 @@
         final Intent bcast = new Intent(ACTION_TETHER_STATE_CHANGED);
         bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
 
-        TetherStatesParcel parcel = buildTetherStatesParcel(null);
+        TetherStatesParcel parcel = buildTetherStatesParcel(null /* cookie */);
         bcast.putStringArrayListExtra(
                 EXTRA_AVAILABLE_TETHER, toIfaces(Arrays.asList(parcel.availableList)));
         bcast.putStringArrayListExtra(
@@ -2194,9 +2203,9 @@
                         break;
                     }
                     case EVENT_REQUEST_CHANGE_DOWNSTREAM: {
-                        final int tetheringType = message.arg1;
-                        final Boolean enabled = (Boolean) message.obj;
-                        enableTetheringInternal(tetheringType, enabled, null);
+                        final boolean enabled = message.arg1 == 1;
+                        final TetheringRequest request = (TetheringRequest) message.obj;
+                        enableTetheringInternal(request.getTetheringType(), enabled, null);
                         break;
                     }
                     default:
@@ -2393,11 +2402,12 @@
 
     /** Register tethering event callback */
     void registerTetheringEventCallback(ITetheringEventCallback callback) {
+        final int uid = mDeps.getBinderCallingUid();
         final boolean hasSystemPrivilege = hasCallingPermission(NETWORK_SETTINGS)
                 || hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)
                 || hasCallingPermission(NETWORK_STACK);
         mHandler.post(() -> {
-            CallbackCookie cookie = new CallbackCookie(hasSystemPrivilege);
+            CallbackCookie cookie = new CallbackCookie(uid, hasSystemPrivilege);
             mTetheringEventCallbacks.register(callback, cookie);
             final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
             parcel.supportedTypes = mSupportedTypeBitmap;
@@ -2492,8 +2502,8 @@
 
         if (DBG) {
             // Use a CallbackCookie with system privilege so nothing is redacted.
-            TetherStatesParcel parcel =
-                    buildTetherStatesParcel(new CallbackCookie(true /* hasSystemPrivilege */));
+            TetherStatesParcel parcel = buildTetherStatesParcel(
+                    new CallbackCookie(Process.SYSTEM_UID, true /* hasSystemPrivilege */));
             Log.d(TAG, String.format(
                     "sendTetherStatesChangedCallback %s=[%s] %s=[%s] %s=[%s] %s=[%s]",
                     "avail", TextUtils.join(",", Arrays.asList(parcel.availableList)),
@@ -2773,9 +2783,9 @@
         }
 
         @Override
-        public void requestEnableTethering(int tetheringType, boolean enabled) {
+        public void requestEnableTethering(TetheringRequest request, boolean enabled) {
             mTetherMainSM.sendMessage(TetherMainSM.EVENT_REQUEST_CHANGE_DOWNSTREAM,
-                    tetheringType, 0, enabled ? Boolean.TRUE : Boolean.FALSE);
+                    enabled ? 1 : 0, 0, request);
         }
     }
 
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index a4823ca..d89bf4d 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -25,6 +25,7 @@
 import android.net.INetd;
 import android.net.connectivity.ConnectivityInternalApiUtil;
 import android.net.ip.IpServer;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -36,7 +37,6 @@
 import androidx.annotation.RequiresApi;
 
 import com.android.modules.utils.build.SdkLevel;
-import com.android.net.module.util.PrivateAddressCoordinator;
 import com.android.net.module.util.RoutingCoordinatorManager;
 import com.android.net.module.util.RoutingCoordinatorService;
 import com.android.net.module.util.SharedLog;
@@ -201,4 +201,11 @@
     public WearableConnectionManager makeWearableConnectionManager(Context ctx) {
         return new WearableConnectionManager(ctx);
     }
+
+    /**
+     * Wrapper to get the binder calling uid for unit testing.
+     */
+    public int getBinderCallingUid() {
+        return Binder.getCallingUid();
+    }
 }
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
index a0198cc..a29f0c2 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
@@ -85,7 +85,6 @@
     static final int ROAMING_NOTIFICATION_ID = 1003;
     @VisibleForTesting
     static final int NO_ICON_ID = 0;
-    @VisibleForTesting
     static final int DOWNSTREAM_NONE = 0;
     // Refer to TelephonyManager#getSimCarrierId for more details about carrier id.
     @VisibleForTesting
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 d0c036f..17f5081 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -17,7 +17,10 @@
 package com.android.networkstack.tethering;
 
 import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_STACK;
 import static android.content.pm.PackageManager.GET_ACTIVITIES;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
 import static android.hardware.usb.UsbManager.USB_FUNCTION_NCM;
@@ -33,6 +36,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
 import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
@@ -164,6 +168,7 @@
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.SoftApCallback;
+import android.net.wifi.WifiSsid;
 import android.net.wifi.p2p.WifiP2pGroup;
 import android.net.wifi.p2p.WifiP2pInfo;
 import android.net.wifi.p2p.WifiP2pManager;
@@ -190,6 +195,7 @@
 
 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.module.util.CollectionUtils;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.PrivateAddressCoordinator;
@@ -223,6 +229,7 @@
 import java.io.PrintWriter;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -257,6 +264,7 @@
     private static final String TEST_WIFI_REGEX = "test_wlan\\d";
     private static final String TEST_P2P_REGEX = "test_p2p-p2p\\d-.*";
     private static final String TEST_BT_REGEX = "test_pan\\d";
+    private static final int TEST_CALLER_UID = 1000;
     private static final String TEST_CALLER_PKG = "com.test.tethering";
 
     private static final int CELLULAR_NETID = 100;
@@ -328,6 +336,7 @@
 
     private TestConnectivityManager mCm;
     private boolean mForceEthernetServiceUnavailable = false;
+    private int mBinderCallingUid = TEST_CALLER_UID;
 
     private class TestContext extends BroadcastInterceptingContext {
         TestContext(Context base) {
@@ -555,6 +564,11 @@
             }
             return mBluetoothPanShim;
         }
+
+        @Override
+        public int getBinderCallingUid() {
+            return mBinderCallingUid;
+        }
     }
 
     private static LinkProperties buildUpstreamLinkProperties(String interfaceName,
@@ -2335,7 +2349,7 @@
         // 2. Enable wifi tethering.
         UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
         initTetheringUpstream(upstreamState);
-        when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
+        when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
         mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
         mLooper.dispatchAll();
         tetherState = callback.pollTetherStatesChanged();
@@ -2380,6 +2394,105 @@
     }
 
     @Test
+    public void testSoftApConfigInTetheringEventCallback() throws Exception {
+        assumeTrue(SdkLevel.isAtLeastV());
+        when(mContext.checkCallingOrSelfPermission(NETWORK_SETTINGS))
+                .thenReturn(PERMISSION_DENIED);
+        when(mContext.checkCallingOrSelfPermission(NETWORK_STACK))
+                .thenReturn(PERMISSION_DENIED);
+        when(mContext.checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK))
+                .thenReturn(PERMISSION_DENIED);
+        initTetheringOnTestThread();
+        TestTetheringEventCallback callback = new TestTetheringEventCallback();
+        TestTetheringEventCallback differentCallback = new TestTetheringEventCallback();
+        TestTetheringEventCallback settingsCallback = new TestTetheringEventCallback();
+        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();
+        tetheringRequest.setUid(TEST_CALLER_UID);
+        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());
+        // Register callback with different UID
+        mBinderCallingUid = TEST_CALLER_UID + 1;
+        mTethering.registerTetheringEventCallback(differentCallback);
+        mLooper.dispatchAll();
+        differentCallback.expectTetheredClientChanged(Collections.emptyList());
+        differentCallback.expectUpstreamChanged(NULL_NETWORK);
+        differentCallback.expectConfigurationChanged(
+                mTethering.getTetheringConfiguration().toStableParcelable());
+        // Register Settings callback
+        when(mContext.checkCallingOrSelfPermission(NETWORK_SETTINGS))
+                .thenReturn(PERMISSION_GRANTED);
+        mTethering.registerTetheringEventCallback(settingsCallback);
+        mLooper.dispatchAll();
+        settingsCallback.expectTetheredClientChanged(Collections.emptyList());
+        settingsCallback.expectUpstreamChanged(NULL_NETWORK);
+        settingsCallback.expectConfigurationChanged(
+                mTethering.getTetheringConfiguration().toStableParcelable());
+
+        assertTetherStatesNotNullButEmpty(callback.pollTetherStatesChanged());
+        assertTetherStatesNotNullButEmpty(differentCallback.pollTetherStatesChanged());
+        assertTetherStatesNotNullButEmpty(settingsCallback.pollTetherStatesChanged());
+        callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+        UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
+        initTetheringUpstream(upstreamState);
+        when(mWifiManager.startTetheredHotspot(null)).thenReturn(true);
+        mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+        mLooper.dispatchAll();
+        assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
+                callback.pollTetherStatesChanged().availableList);
+        assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
+                differentCallback.pollTetherStatesChanged().availableList);
+        assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
+                settingsCallback.pollTetherStatesChanged().availableList);
+
+        // Enable wifi tethering
+        mBinderCallingUid = TEST_CALLER_UID;
+        mTethering.startTethering(tetheringRequest, TEST_CALLER_PKG, null);
+        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
+        assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
+                callback.pollTetherStatesChanged().tetheredList);
+        assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
+                differentCallback.pollTetherStatesChanged().tetheredList);
+        assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
+                settingsCallback.pollTetherStatesChanged().tetheredList);
+        callback.expectUpstreamChanged(upstreamState.network);
+        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,
+                    IFACE_IP_MODE_TETHERED);
+        }
+        assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
+                callback.pollTetherStatesChanged().availableList);
+        assertArrayEquals(new TetheringInterface[] {wifiIfaceWithoutConfig},
+                differentCallback.pollTetherStatesChanged().availableList);
+        assertArrayEquals(new TetheringInterface[] {wifiIfaceWithConfig},
+                settingsCallback.pollTetherStatesChanged().availableList);
+        mLooper.dispatchAll();
+        callback.expectUpstreamChanged(NULL_NETWORK);
+        callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+        callback.assertNoCallback();
+    }
+
+    @Test
     public void testReportFailCallbackIfOffloadNotSupported() throws Exception {
         initTetheringOnTestThread();
         final UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
diff --git a/tests/cts/hostside/aidl/Android.bp b/tests/cts/hostside/aidl/Android.bp
index 31924f0..ca3e77f 100644
--- a/tests/cts/hostside/aidl/Android.bp
+++ b/tests/cts/hostside/aidl/Android.bp
@@ -19,8 +19,7 @@
 
 java_test_helper_library {
     name: "CtsHostsideNetworkTestsAidl",
-    sdk_version: "current",
-    min_sdk_version: "30",
+    sdk_version: "system_current",
     srcs: [
         "com/android/cts/net/hostside/*.aidl",
         "com/android/cts/net/hostside/*.java",
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/ITetheringHelper.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/ITetheringHelper.aidl
new file mode 100644
index 0000000..a9f5ed4
--- /dev/null
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/ITetheringHelper.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.cts.net.hostside;
+
+import android.net.TetheringInterface;
+
+interface ITetheringHelper {
+    TetheringInterface getTetheredWifiInterface();
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringHelperClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringHelperClient.java
new file mode 100644
index 0000000..5f5ebb0
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringHelperClient.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 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.cts.net.hostside;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.TetheringInterface;
+import android.os.ConditionVariable;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+public class TetheringHelperClient {
+    private static final int TIMEOUT_MS = 5000;
+    private static final String PACKAGE = TetheringHelperClient.class.getPackage().getName();
+    private static final String APP2_PACKAGE = PACKAGE + ".app2";
+    private static final String SERVICE_NAME = APP2_PACKAGE + ".TetheringHelperService";
+
+    private Context mContext;
+    private ServiceConnection mServiceConnection;
+    private ITetheringHelper mService;
+
+    public TetheringHelperClient(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Binds to TetheringHelperService.
+     */
+    public void bind() {
+        if (mService != null) {
+            throw new IllegalStateException("Already bound");
+        }
+
+        final ConditionVariable cv = new ConditionVariable();
+        mServiceConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                mService = ITetheringHelper.Stub.asInterface(service);
+                cv.open();
+            }
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                mService = null;
+            }
+        };
+
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName(APP2_PACKAGE, SERVICE_NAME));
+        mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+        cv.block(TIMEOUT_MS);
+        if (mService == null) {
+            throw new IllegalStateException(
+                    "Could not bind to TetheringHelperService after " + TIMEOUT_MS + "ms");
+        }
+    }
+
+    /**
+     * Unbinds from TetheringHelperService.
+     */
+    public void unbind() {
+        if (mService != null) {
+            mContext.unbindService(mServiceConnection);
+        }
+    }
+
+    /**
+     * Returns the tethered Wifi interface as seen from TetheringHelperService.
+     */
+    public TetheringInterface getTetheredWifiInterface() throws RemoteException {
+        return mService.getTetheredWifiInterface();
+    }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringTest.java
new file mode 100644
index 0000000..ad98a29
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/TetheringTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 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.cts.net.hostside;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.net.TetheringInterface;
+import android.net.cts.util.CtsTetheringUtils;
+import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.WifiSsid;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+
+public class TetheringTest {
+    private CtsTetheringUtils mCtsTetheringUtils;
+    private TetheringHelperClient mTetheringHelperClient;
+
+    @Before
+    public void setUp() throws Exception {
+        Context targetContext = getInstrumentation().getTargetContext();
+        mCtsTetheringUtils = new CtsTetheringUtils(targetContext);
+        mTetheringHelperClient = new TetheringHelperClient(targetContext);
+        mTetheringHelperClient.bind();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTetheringHelperClient.unbind();
+    }
+
+    /**
+     * Starts Wifi tethering and tests that the SoftApConfiguration is redacted from
+     * TetheringEventCallback for other apps.
+     */
+    @Test
+    public void testSoftApConfigurationRedactedForOtherUids() throws Exception {
+        final CtsTetheringUtils.TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
+        SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+                .setWifiSsid(WifiSsid.fromBytes("This is an SSID!"
+                        .getBytes(StandardCharsets.UTF_8))).build();
+        final TetheringInterface tetheringInterface =
+                mCtsTetheringUtils.startWifiTethering(tetherEventCallback, softApConfig);
+        assertNotNull(tetheringInterface);
+        assertEquals(softApConfig, tetheringInterface.getSoftApConfiguration());
+        try {
+            TetheringInterface tetheringInterfaceForApp2 =
+                    mTetheringHelperClient.getTetheredWifiInterface();
+            assertNotNull(tetheringInterfaceForApp2);
+            assertNull(tetheringInterfaceForApp2.getSoftApConfiguration());
+            assertEquals(
+                    tetheringInterface.getInterface(), tetheringInterfaceForApp2.getInterface());
+        } finally {
+            mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);
+        }
+    }
+}
diff --git a/tests/cts/hostside/app2/AndroidManifest.xml b/tests/cts/hostside/app2/AndroidManifest.xml
index 412b307..6ccaf4f 100644
--- a/tests/cts/hostside/app2/AndroidManifest.xml
+++ b/tests/cts/hostside/app2/AndroidManifest.xml
@@ -42,6 +42,8 @@
             android:debuggable="true">
         <service android:name=".RemoteSocketFactoryService"
              android:exported="true"/>
+        <service android:name=".TetheringHelperService"
+             android:exported="true"/>
     </application>
 
     <!--
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/TetheringHelperService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/TetheringHelperService.java
new file mode 100644
index 0000000..56a8cbb
--- /dev/null
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/TetheringHelperService.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 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.cts.net.hostside.app2;
+
+
+import static android.net.TetheringManager.TETHERING_WIFI;
+
+import android.app.Service;
+import android.content.Intent;
+import android.net.TetheringInterface;
+import android.net.TetheringManager;
+import android.os.IBinder;
+
+import androidx.annotation.NonNull;
+
+import com.android.cts.net.hostside.ITetheringHelper;
+
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class TetheringHelperService extends Service {
+    private static final String TAG = TetheringHelperService.class.getSimpleName();
+
+    private ITetheringHelper.Stub mBinder = new ITetheringHelper.Stub() {
+        public TetheringInterface getTetheredWifiInterface() {
+            ArrayBlockingQueue<TetheringInterface> queue = new ArrayBlockingQueue<>(1);
+            TetheringManager.TetheringEventCallback callback =
+                    new TetheringManager.TetheringEventCallback() {
+                        @Override
+                        public void onTetheredInterfacesChanged(
+                                @NonNull Set<TetheringInterface> interfaces) {
+                            for (TetheringInterface iface : interfaces) {
+                                if (iface.getType() == TETHERING_WIFI) {
+                                    queue.offer(iface);
+                                    break;
+                                }
+                            }
+                        }
+                    };
+            TetheringManager tm =
+                    TetheringHelperService.this.getSystemService(TetheringManager.class);
+            tm.registerTetheringEventCallback(Runnable::run, callback);
+            TetheringInterface iface;
+            try {
+                iface = queue.poll(5, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new IllegalStateException("Wait for wifi TetheredInterface interrupted");
+            } finally {
+                tm.unregisterTetheringEventCallback(callback);
+            }
+            if (iface == null) {
+                throw new IllegalStateException(
+                        "No wifi TetheredInterface received after 5 seconds");
+            }
+            return iface;
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideTetheringTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideTetheringTests.java
new file mode 100644
index 0000000..d73e01a
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideTetheringTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 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.cts.net;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class HostsideTetheringTests extends HostsideNetworkTestCase {
+    /**
+     * Set up the test once before running all the tests.
+     */
+    @BeforeClassWithInfo
+    public static void setUpOnce(TestInformation testInfo) throws Exception {
+        uninstallPackage(testInfo, TEST_APP2_PKG, false);
+        installPackage(testInfo, TEST_APP2_APK);
+    }
+
+    /**
+     * Tear down the test once after running all the tests.
+     */
+    @AfterClassWithInfo
+    public static void tearDownOnce(TestInformation testInfo)
+            throws DeviceNotAvailableException {
+        uninstallPackage(testInfo, TEST_APP2_PKG, true);
+    }
+
+    @Test
+    public void testSoftApConfigurationRedactedForOtherApps() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".TetheringTest",
+                "testSoftApConfigurationRedactedForOtherUids");
+    }
+}
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 dffd9d5..243cd27 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
@@ -46,6 +46,7 @@
 import android.net.TetheringManager.TetheringEventCallback;
 import android.net.TetheringManager.TetheringInterfaceRegexps;
 import android.net.TetheringManager.TetheringRequest;
+import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.SoftApCallback;
@@ -491,13 +492,29 @@
         }
     }
 
+    /**
+     * Starts Wi-Fi tethering.
+     */
     public TetheringInterface startWifiTethering(final TestTetheringEventCallback callback)
             throws InterruptedException {
+        return startWifiTethering(callback, null);
+    }
+
+    /**
+     * Starts Wi-Fi tethering with the specified SoftApConfiguration.
+     */
+    public TetheringInterface startWifiTethering(final TestTetheringEventCallback callback,
+            final SoftApConfiguration softApConfiguration)
+            throws InterruptedException {
         final List<String> wifiRegexs = getWifiTetherableInterfaceRegexps(callback);
 
         final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
-        final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
-                .setShouldShowEntitlementUi(false).build();
+        TetheringRequest.Builder builder = new TetheringRequest.Builder(TETHERING_WIFI)
+                .setShouldShowEntitlementUi(false);
+        if (softApConfiguration != null) {
+            builder.setSoftApConfiguration(softApConfiguration);
+        }
+        final TetheringRequest request = builder.build();
 
         return runAsShell(TETHER_PRIVILEGED, () -> {
             mTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index a07c9ea..47d444f 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -22,6 +22,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
 import static android.net.TetheringManager.TETHERING_ETHERNET;
 import static android.net.TetheringManager.TETHERING_NCM;
@@ -244,9 +246,16 @@
         assertNull(tr.getClientStaticIpv4Address());
         assertFalse(tr.isExemptFromEntitlementCheck());
         assertTrue(tr.getShouldShowEntitlementUi());
+        assertEquals(CONNECTIVITY_SCOPE_GLOBAL, tr.getConnectivityScope());
         assertEquals(softApConfiguration, tr.getSoftApConfiguration());
         assertEquals(INVALID_UID, tr.getUid());
         assertNull(tr.getPackageName());
+        assertEquals(tr.toString(), "TetheringRequest[ "
+                + "TETHERING_WIFI, "
+                + "showProvisioningUi, "
+                + "CONNECTIVITY_SCOPE_GLOBAL, "
+                + "softApConfig=" + softApConfiguration.toString()
+                + " ]");
 
         final LinkAddress localAddr = new LinkAddress("192.168.24.5/24");
         final LinkAddress clientAddr = new LinkAddress("192.168.24.100/24");
@@ -254,6 +263,7 @@
                 .setStaticIpv4Addresses(localAddr, clientAddr)
                 .setExemptFromEntitlementCheck(true)
                 .setShouldShowEntitlementUi(false)
+                .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL)
                 .build();
         int uid = 1000;
         String packageName = "package";
@@ -265,13 +275,26 @@
         assertEquals(TETHERING_USB, tr2.getTetheringType());
         assertTrue(tr2.isExemptFromEntitlementCheck());
         assertFalse(tr2.getShouldShowEntitlementUi());
+        assertEquals(CONNECTIVITY_SCOPE_LOCAL, tr2.getConnectivityScope());
+        assertNull(tr2.getSoftApConfiguration());
         assertEquals(uid, tr2.getUid());
         assertEquals(packageName, tr2.getPackageName());
+        assertEquals(tr2.toString(), "TetheringRequest[ "
+                + "TETHERING_USB, "
+                + "localIpv4Address=" + localAddr + ", "
+                + "staticClientAddress=" + clientAddr + ", "
+                + "exemptFromEntitlementCheck, "
+                + "CONNECTIVITY_SCOPE_LOCAL, "
+                + "uid=1000, "
+                + "packageName=package"
+                + " ]");
 
         final TetheringRequest tr3 = new TetheringRequest.Builder(TETHERING_USB)
                 .setStaticIpv4Addresses(localAddr, clientAddr)
                 .setExemptFromEntitlementCheck(true)
-                .setShouldShowEntitlementUi(false).build();
+                .setShouldShowEntitlementUi(false)
+                .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL)
+                .build();
         tr3.setUid(uid);
         tr3.setPackageName(packageName);
         assertEquals(tr2, tr3);
@@ -340,10 +363,14 @@
             tetherEventCallback.assumeWifiTetheringSupported(mContext);
             tetherEventCallback.expectNoTetheringActive();
 
+            SoftApConfiguration softApConfig = new SoftApConfiguration.Builder()
+                    .setWifiSsid(WifiSsid.fromBytes("This is an SSID!"
+                            .getBytes(StandardCharsets.UTF_8))).build();
             final TetheringInterface tetheredIface =
-                    mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
+                    mCtsTetheringUtils.startWifiTethering(tetherEventCallback, softApConfig);
 
             assertNotNull(tetheredIface);
+            assertEquals(softApConfig, tetheredIface.getSoftApConfiguration());
             final String wifiTetheringIface = tetheredIface.getInterface();
 
             mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);