Include SoftAP config in TetheringEventCallback
Include SoftApConfiguration in TetheringEventCallback via
TetheringInterface, to allow apps to know which interfaces are created
with their SoftApConfigurations.
API-Coverage-Bug: 371133403
Bug: 216524590
Test: atest TetheringTest CtsTetheringTest
Change-Id: I1b4868ba8327bee3b03c0f21e5b4f0845f2f24a8
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/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index a0604f2..e677dd5 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;
@@ -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);
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 61833c2..bcf1606 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(
@@ -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)),
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();