diff --git a/Tethering/Android.bp b/Tethering/Android.bp index 29f6e126a5..026ed5406e 100644 --- a/Tethering/Android.bp +++ b/Tethering/Android.bp @@ -33,6 +33,7 @@ java_defaults { ":framework-connectivity-shared-srcs", ":tethering-module-utils-srcs", ":services-tethering-shared-srcs", + ":statslog-tethering-java-gen", ], static_libs: [ "androidx.annotation_annotation", @@ -223,3 +224,11 @@ java_library_static { apex_available: ["com.android.tethering"], min_sdk_version: "30", } + +genrule { + name: "statslog-tethering-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module network_tethering" + + " --javaPackage com.android.networkstack.tethering.metrics --javaClass TetheringStatsLog", + out: ["com/android/networkstack/tethering/metrics/TetheringStatsLog.java"], +} \ No newline at end of file diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java index c718f4c9a0..437ed71c21 100644 --- a/Tethering/src/android/net/ip/IpServer.java +++ b/Tethering/src/android/net/ip/IpServer.java @@ -69,6 +69,7 @@ import com.android.networkstack.tethering.BpfCoordinator.ClientInfo; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.networkstack.tethering.PrivateAddressCoordinator; import com.android.networkstack.tethering.TetheringConfiguration; +import com.android.networkstack.tethering.metrics.TetheringMetrics; import com.android.networkstack.tethering.util.InterfaceSet; import com.android.networkstack.tethering.util.PrefixUtils; @@ -282,13 +283,15 @@ public class IpServer extends StateMachine { private LinkAddress mIpv4Address; + private final TetheringMetrics mTetheringMetrics; + // TODO: Add a dependency object to pass the data members or variables from the tethering // object. It helps to reduce the arguments of the constructor. public IpServer( String ifaceName, Looper looper, int interfaceType, SharedLog log, INetd netd, @NonNull BpfCoordinator coordinator, Callback callback, TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator, - Dependencies deps) { + TetheringMetrics tetheringMetrics, Dependencies deps) { super(ifaceName, looper); mLog = log.forSubComponent(ifaceName); mNetd = netd; @@ -303,6 +306,7 @@ public class IpServer extends StateMachine { mP2pLeasesSubnetPrefixLength = config.getP2pLeasesSubnetPrefixLength(); mPrivateAddressCoordinator = addressCoordinator; mDeps = deps; + mTetheringMetrics = tetheringMetrics; resetLinkProperties(); mLastError = TetheringManager.TETHER_ERROR_NO_ERROR; mServingMode = STATE_AVAILABLE; @@ -1201,6 +1205,9 @@ public class IpServer extends StateMachine { stopConntrackMonitoring(); resetLinkProperties(); + + mTetheringMetrics.updateErrorCode(mInterfaceType, mLastError); + mTetheringMetrics.sendReport(mInterfaceType); } @Override diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index 44935fc8c8..551fd633e1 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -140,6 +140,7 @@ import com.android.networkstack.apishim.common.BluetoothPanShim; import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim; import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim; import com.android.networkstack.apishim.common.UnsupportedApiLevelException; +import com.android.networkstack.tethering.metrics.TetheringMetrics; import com.android.networkstack.tethering.util.InterfaceSet; import com.android.networkstack.tethering.util.PrefixUtils; import com.android.networkstack.tethering.util.TetheringUtils; @@ -254,6 +255,7 @@ public class Tethering { private final UserManager mUserManager; private final BpfCoordinator mBpfCoordinator; private final PrivateAddressCoordinator mPrivateAddressCoordinator; + private final TetheringMetrics mTetheringMetrics; private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID; private volatile TetheringConfiguration mConfig; @@ -292,6 +294,7 @@ public class Tethering { mNetd = mDeps.getINetd(mContext); mLooper = mDeps.getTetheringLooper(); mNotificationUpdater = mDeps.getNotificationUpdater(mContext, mLooper); + mTetheringMetrics = mDeps.getTetheringMetrics(); // 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 @@ -616,7 +619,8 @@ public class Tethering { processInterfaceStateChange(iface, false /* enabled */); } - void startTethering(final TetheringRequestParcel request, final IIntResultListener listener) { + void startTethering(final TetheringRequestParcel request, final String callerPkg, + final IIntResultListener listener) { mHandler.post(() -> { final TetheringRequestParcel unfinishedRequest = mActiveTetheringRequests.get( request.tetheringType); @@ -636,6 +640,7 @@ public class Tethering { request.showProvisioningUi); } enableTetheringInternal(request.tetheringType, true /* enabled */, listener); + mTetheringMetrics.createBuilder(request.tetheringType, callerPkg); }); } @@ -695,7 +700,11 @@ public class Tethering { // If changing tethering fail, remove corresponding request // no matter who trigger the start/stop. - if (result != TETHER_ERROR_NO_ERROR) mActiveTetheringRequests.remove(type); + if (result != TETHER_ERROR_NO_ERROR) { + mActiveTetheringRequests.remove(type); + mTetheringMetrics.updateErrorCode(type, result); + mTetheringMetrics.sendReport(type); + } } private int setWifiTethering(final boolean enable) { @@ -2749,7 +2758,7 @@ public class Tethering { final TetherState tetherState = new TetherState( new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator, makeControlCallback(), mConfig, mPrivateAddressCoordinator, - mDeps.getIpServerDependencies()), isNcm); + mTetheringMetrics, mDeps.getIpServerDependencies()), isNcm); mTetherStates.put(iface, tetherState); tetherState.ipServer.start(); } diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java index 9224213f0e..8e0354d082 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java @@ -34,6 +34,7 @@ import androidx.annotation.NonNull; import com.android.internal.util.StateMachine; import com.android.networkstack.apishim.BluetoothPanShimImpl; import com.android.networkstack.apishim.common.BluetoothPanShim; +import com.android.networkstack.tethering.metrics.TetheringMetrics; import java.util.ArrayList; @@ -163,4 +164,11 @@ public abstract class TetheringDependencies { public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) { return BluetoothPanShimImpl.newInstance(pan); } + + /** + * Get a reference to the TetheringMetrics to be used by tethering. + */ + public TetheringMetrics getTetheringMetrics() { + return new TetheringMetrics(); + } } diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java index 9fb61fe4d9..f147e1036d 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java +++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java @@ -137,7 +137,7 @@ public class TetheringService extends Service { return; } - mTethering.startTethering(request, listener); + mTethering.startTethering(request, callerPkg, listener); } @Override diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java new file mode 100644 index 0000000000..e25f2ae1ed --- /dev/null +++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2022 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.metrics; + +import static android.net.TetheringManager.TETHERING_BLUETOOTH; +import static android.net.TetheringManager.TETHERING_ETHERNET; +import static android.net.TetheringManager.TETHERING_NCM; +import static android.net.TetheringManager.TETHERING_USB; +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHERING_WIFI_P2P; +import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; +import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; +import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; +import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; +import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; +import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; +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_UNSUPPORTED; +import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; + +import android.stats.connectivity.DownstreamType; +import android.stats.connectivity.ErrorCode; +import android.stats.connectivity.UpstreamType; +import android.stats.connectivity.UserType; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +/** + * Collection of utilities for tethering metrics. + * + * To see if the logs are properly sent to statsd, execute following commands + * + * $ adb shell cmd stats print-logs + * $ adb logcat | grep statsd OR $ adb logcat -b stats + * + * @hide + */ +public class TetheringMetrics { + private static final String TAG = TetheringMetrics.class.getSimpleName(); + private static final boolean DBG = false; + private static final String SETTINGS_PKG_NAME = "com.android.settings"; + private static final String SYSTEMUI_PKG_NAME = "com.android.systemui"; + private static final String GMS_PKG_NAME = "com.google.android.gms"; + private final SparseArray mBuilderMap = new SparseArray<>(); + + /** Update Tethering stats about caller's package name and downstream type. */ + public void createBuilder(final int downstreamType, final String callerPkg) { + mBuilderMap.clear(); + NetworkTetheringReported.Builder statsBuilder = + NetworkTetheringReported.newBuilder(); + statsBuilder.setDownstreamType(downstreamTypeToEnum(downstreamType)) + .setUserType(userTypeToEnum(callerPkg)) + .setUpstreamType(UpstreamType.UT_UNKNOWN) + .setErrorCode(ErrorCode.EC_NO_ERROR) + .build(); + mBuilderMap.put(downstreamType, statsBuilder); + } + + /** Update error code of given downstreamType. */ + public void updateErrorCode(final int downstreamType, final int errCode) { + NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType); + if (statsBuilder == null) { + Log.e(TAG, "Given downstreamType does not exist, this is a bug!"); + return; + } + statsBuilder.setErrorCode(errorCodeToEnum(errCode)); + } + + /** Remove Tethering stats. + * If Tethering stats is ready to write then write it before removing. + */ + public void sendReport(final int downstreamType) { + final NetworkTetheringReported.Builder statsBuilder = + mBuilderMap.get(downstreamType); + if (statsBuilder == null) { + Log.e(TAG, "Given downstreamType does not exist, this is a bug!"); + return; + } + write(statsBuilder.build()); + mBuilderMap.remove(downstreamType); + } + + /** Collect Tethering stats and write metrics data to statsd pipeline. */ + @VisibleForTesting + public void write(@NonNull final NetworkTetheringReported reported) { + TetheringStatsLog.write(TetheringStatsLog.NETWORK_TETHERING_REPORTED, + reported.getErrorCode().getNumber(), + reported.getDownstreamType().getNumber(), + reported.getUpstreamType().getNumber(), + reported.getUserType().getNumber()); + if (DBG) { + Log.d(TAG, "Write errorCode: " + reported.getErrorCode().getNumber() + + ", downstreamType: " + reported.getDownstreamType().getNumber() + + ", upstreamType: " + reported.getUpstreamType().getNumber() + + ", userType: " + reported.getUserType().getNumber()); + } + } + + /** Map {@link TetheringType} to {@link DownstreamType} */ + private DownstreamType downstreamTypeToEnum(final int ifaceType) { + switch(ifaceType) { + case TETHERING_WIFI: + return DownstreamType.DS_TETHERING_WIFI; + case TETHERING_WIFI_P2P: + return DownstreamType.DS_TETHERING_WIFI_P2P; + case TETHERING_USB: + return DownstreamType.DS_TETHERING_USB; + case TETHERING_BLUETOOTH: + return DownstreamType.DS_TETHERING_BLUETOOTH; + case TETHERING_NCM: + return DownstreamType.DS_TETHERING_NCM; + case TETHERING_ETHERNET: + return DownstreamType.DS_TETHERING_ETHERNET; + default: + return DownstreamType.DS_UNSPECIFIED; + } + } + + /** Map {@link StartTetheringError} to {@link ErrorCode} */ + private ErrorCode errorCodeToEnum(final int lastError) { + switch(lastError) { + case TETHER_ERROR_NO_ERROR: + return ErrorCode.EC_NO_ERROR; + case TETHER_ERROR_UNKNOWN_IFACE: + return ErrorCode.EC_UNKNOWN_IFACE; + case TETHER_ERROR_SERVICE_UNAVAIL: + return ErrorCode.EC_SERVICE_UNAVAIL; + case TETHER_ERROR_UNSUPPORTED: + return ErrorCode.EC_UNSUPPORTED; + case TETHER_ERROR_UNAVAIL_IFACE: + return ErrorCode.EC_UNAVAIL_IFACE; + case TETHER_ERROR_INTERNAL_ERROR: + return ErrorCode.EC_INTERNAL_ERROR; + case TETHER_ERROR_TETHER_IFACE_ERROR: + return ErrorCode.EC_TETHER_IFACE_ERROR; + case TETHER_ERROR_UNTETHER_IFACE_ERROR: + return ErrorCode.EC_UNTETHER_IFACE_ERROR; + case TETHER_ERROR_ENABLE_FORWARDING_ERROR: + return ErrorCode.EC_ENABLE_FORWARDING_ERROR; + case TETHER_ERROR_DISABLE_FORWARDING_ERROR: + return ErrorCode.EC_DISABLE_FORWARDING_ERROR; + case TETHER_ERROR_IFACE_CFG_ERROR: + return ErrorCode.EC_IFACE_CFG_ERROR; + case TETHER_ERROR_PROVISIONING_FAILED: + return ErrorCode.EC_PROVISIONING_FAILED; + case TETHER_ERROR_DHCPSERVER_ERROR: + return ErrorCode.EC_DHCPSERVER_ERROR; + case TETHER_ERROR_ENTITLEMENT_UNKNOWN: + return ErrorCode.EC_ENTITLEMENT_UNKNOWN; + case TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION: + return ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION; + case TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION: + return ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION; + default: + return ErrorCode.EC_UNKNOWN_TYPE; + } + } + + /** Map callerPkg to {@link UserType} */ + private UserType userTypeToEnum(final String callerPkg) { + if (callerPkg.equals(SETTINGS_PKG_NAME)) { + return UserType.USER_SETTINGS; + } else if (callerPkg.equals(SYSTEMUI_PKG_NAME)) { + return UserType.USER_SYSTEMUI; + } else if (callerPkg.equals(GMS_PKG_NAME)) { + return UserType.USER_GMS; + } else { + return UserType.USER_UNKNOWN; + } + } +} diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index aac531acc5..bf7e887986 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -116,6 +116,7 @@ import com.android.networkstack.tethering.TetherLimitKey; import com.android.networkstack.tethering.TetherLimitValue; import com.android.networkstack.tethering.TetherUpstream6Key; import com.android.networkstack.tethering.TetheringConfiguration; +import com.android.networkstack.tethering.metrics.TetheringMetrics; import com.android.networkstack.tethering.util.InterfaceSet; import com.android.networkstack.tethering.util.PrefixUtils; import com.android.testutils.DevSdkIgnoreRule; @@ -186,6 +187,7 @@ public class IpServerTest { @Mock private NetworkStatsManager mStatsManager; @Mock private TetheringConfiguration mTetherConfig; @Mock private ConntrackMonitor mConntrackMonitor; + @Mock private TetheringMetrics mTetheringMetrics; @Mock private BpfMap mBpfDownstream4Map; @Mock private BpfMap mBpfUpstream4Map; @Mock private BpfMap mBpfDownstream6Map; @@ -235,7 +237,7 @@ public class IpServerTest { when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH); mIpServer = new IpServer( IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator, - mCallback, mTetherConfig, mAddressCoordinator, mDependencies); + mCallback, mTetherConfig, mAddressCoordinator, mTetheringMetrics, mDependencies); mIpServer.start(); mNeighborEventConsumer = neighborCaptor.getValue(); @@ -367,7 +369,7 @@ public class IpServerTest { .thenReturn(mIpNeighborMonitor); mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog, mNetd, mBpfCoordinator, mCallback, mTetherConfig, mAddressCoordinator, - mDependencies); + mTetheringMetrics, mDependencies); mIpServer.start(); mLooper.dispatchAll(); verify(mCallback).updateInterfaceState( @@ -451,6 +453,9 @@ public class IpServerTest { mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), any(LinkProperties.class)); + verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_BLUETOOTH), + eq(TETHER_ERROR_NO_ERROR)); + verify(mTetheringMetrics).sendReport(eq(TETHERING_BLUETOOTH)); verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator); } @@ -658,6 +663,9 @@ public class IpServerTest { usbTeardownOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), mLinkPropertiesCaptor.capture()); assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); + verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_USB), + eq(TETHER_ERROR_TETHER_IFACE_ERROR)); + verify(mTetheringMetrics).sendReport(eq(TETHERING_USB)); } @Test @@ -676,6 +684,9 @@ public class IpServerTest { usbTeardownOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), mLinkPropertiesCaptor.capture()); assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); + verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_USB), + eq(TETHER_ERROR_ENABLE_FORWARDING_ERROR)); + verify(mTetheringMetrics).sendReport(eq(TETHERING_USB)); } @Test diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java index f664d5d615..9db8f1687e 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java @@ -275,7 +275,7 @@ public final class TetheringServiceTest { mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); verify(mTethering).isTetheringSupported(); - verify(mTethering).startTethering(eq(request), eq(result)); + verify(mTethering).startTethering(eq(request), eq(TEST_CALLER_PKG), eq(result)); } @Test 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 2fd7f4802d..4662c968a3 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -45,6 +45,7 @@ import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHERING_WIFI_P2P; import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; @@ -192,6 +193,7 @@ import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfac import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim; import com.android.networkstack.apishim.common.UnsupportedApiLevelException; import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent; +import com.android.networkstack.tethering.metrics.TetheringMetrics; import com.android.testutils.MiscAsserts; import org.junit.After; @@ -239,6 +241,7 @@ public class TetheringTest { 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 String TEST_CALLER_PKG = "com.test.tethering"; private static final int CELLULAR_NETID = 100; private static final int WIFI_NETID = 101; @@ -273,6 +276,7 @@ public class TetheringTest { @Mock private BluetoothPan mBluetoothPan; @Mock private BluetoothPanShim mBluetoothPanShim; @Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim; + @Mock private TetheringMetrics mTetheringMetrics; private final MockIpServerDependencies mIpServerDependencies = spy(new MockIpServerDependencies()); @@ -496,6 +500,11 @@ public class TetheringTest { return false; } + @Override + public TetheringMetrics getTetheringMetrics() { + return mTetheringMetrics; + } + @Override public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx, TetheringConfiguration cfg) { @@ -855,7 +864,8 @@ public class TetheringTest { private void prepareNcmTethering() { // Emulate startTethering(TETHERING_NCM) called - mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG, + null); mLooper.dispatchAll(); verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM); } @@ -863,7 +873,7 @@ public class TetheringTest { private void prepareUsbTethering() { // Emulate pressing the USB tethering button in Settings UI. final TetheringRequestParcel request = createTetheringRequestParcel(TETHERING_USB); - mTethering.startTethering(request, null); + mTethering.startTethering(request, TEST_CALLER_PKG, null); mLooper.dispatchAll(); assertEquals(1, mTethering.getActiveTetheringRequests().size()); @@ -1433,7 +1443,8 @@ public class TetheringTest { when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG, + null); mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); @@ -1460,7 +1471,8 @@ public class TetheringTest { when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG, + null); mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); @@ -1536,11 +1548,13 @@ public class TetheringTest { doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG, + null); mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); verifyNoMoreInteractions(mNetd); + verify(mTetheringMetrics).createBuilder(eq(TETHERING_WIFI), anyString()); // Emulate externally-visible WifiManager effects, causing the // per-interface state machine to start up, and telling us that @@ -1579,6 +1593,10 @@ public class TetheringTest { verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR); + verify(mTetheringMetrics, times(2)).updateErrorCode(eq(TETHERING_WIFI), + eq(TETHER_ERROR_INTERNAL_ERROR)); + verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_WIFI)); + verifyNoMoreInteractions(mWifiManager); verifyNoMoreInteractions(mNetd); } @@ -1881,7 +1899,8 @@ public class TetheringTest { tetherState = callback.pollTetherStatesChanged(); assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface}); - mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG, + null); sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); tetherState = callback.pollTetherStatesChanged(); assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIface}); @@ -1978,10 +1997,12 @@ public class TetheringTest { public void testNoDuplicatedEthernetRequest() throws Exception { final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class); when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest); - mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG, + null); mLooper.dispatchAll(); verify(mEm, times(1)).requestTetheredInterface(any(), any()); - mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG, + null); mLooper.dispatchAll(); verifyNoMoreInteractions(mEm); mTethering.stopTethering(TETHERING_ETHERNET); @@ -2185,14 +2206,16 @@ public class TetheringTest { final ResultListener thirdResult = new ResultListener(TETHER_ERROR_NO_ERROR); // Enable USB tethering and check that Tethering starts USB. - mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), firstResult); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG, + firstResult); mLooper.dispatchAll(); firstResult.assertHasResult(); verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); verifyNoMoreInteractions(mUsbManager); // Enable USB tethering again with the same request and expect no change to USB. - mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), secondResult); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG, + secondResult); mLooper.dispatchAll(); secondResult.assertHasResult(); verify(mUsbManager, never()).setCurrentFunctions(UsbManager.FUNCTION_NONE); @@ -2201,7 +2224,8 @@ public class TetheringTest { // Enable USB tethering with a different request and expect that USB is stopped and // started. mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, - serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), thirdResult); + serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), + TEST_CALLER_PKG, thirdResult); mLooper.dispatchAll(); thirdResult.assertHasResult(); verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE); @@ -2230,7 +2254,8 @@ public class TetheringTest { final ArgumentCaptor dhcpParamsCaptor = ArgumentCaptor.forClass(DhcpServingParamsParcel.class); mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, - serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), null); + serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), + TEST_CALLER_PKG, null); mLooper.dispatchAll(); verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM); mTethering.interfaceStatusChanged(TEST_NCM_IFNAME, true); @@ -2298,7 +2323,7 @@ public class TetheringTest { final TetheringRequestParcel wifiNotExemptRequest = createTetheringRequestParcel(TETHERING_WIFI, null, null, false, CONNECTIVITY_SCOPE_GLOBAL); - mTethering.startTethering(wifiNotExemptRequest, null); + mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null); mLooper.dispatchAll(); verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false); verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI); @@ -2312,7 +2337,7 @@ public class TetheringTest { final TetheringRequestParcel wifiExemptRequest = createTetheringRequestParcel(TETHERING_WIFI, null, null, true, CONNECTIVITY_SCOPE_GLOBAL); - mTethering.startTethering(wifiExemptRequest, null); + mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null); mLooper.dispatchAll(); verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false); verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI); @@ -2325,14 +2350,14 @@ public class TetheringTest { // If one app enables tethering without provisioning check first, then another app enables // tethering of the same type but does not disable the provisioning check. setupForRequiredProvisioning(); - mTethering.startTethering(wifiExemptRequest, null); + mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null); mLooper.dispatchAll(); verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false); verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI); assertTrue(mEntitleMgr.isCellularUpstreamPermitted()); reset(mEntitleMgr); setupForRequiredProvisioning(); - mTethering.startTethering(wifiNotExemptRequest, null); + mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null); mLooper.dispatchAll(); verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false); verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI); @@ -2422,7 +2447,8 @@ public class TetheringTest { when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest); final ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass(TetheredInterfaceCallback.class); - mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), + TEST_CALLER_PKG, null); mLooper.dispatchAll(); verify(mEm).requestTetheredInterface(any(), callbackCaptor.capture()); TetheredInterfaceCallback ethCallback = callbackCaptor.getValue(); @@ -2597,7 +2623,8 @@ public class TetheringTest { final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR); mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */); - mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), + TEST_CALLER_PKG, result); mLooper.dispatchAll(); verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */); result.assertHasResult(); @@ -2632,7 +2659,8 @@ public class TetheringTest { final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR); mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */); - mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), + TEST_CALLER_PKG, result); mLooper.dispatchAll(); verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */); result.assertHasResult(); @@ -2653,7 +2681,8 @@ public class TetheringTest { // already bound. mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */); final ResultListener secondResult = new ResultListener(TETHER_ERROR_NO_ERROR); - mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), secondResult); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), + TEST_CALLER_PKG, secondResult); mLooper.dispatchAll(); verifySetBluetoothTethering(true /* enable */, false /* bindToPanService */); secondResult.assertHasResult(); @@ -2674,7 +2703,8 @@ public class TetheringTest { public void testBluetoothServiceDisconnects() throws Exception { final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR); mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */); - mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), + TEST_CALLER_PKG, result); mLooper.dispatchAll(); ServiceListener panListener = verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */); @@ -2825,18 +2855,26 @@ public class TetheringTest { runNcmTethering(); verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks( any(), any()); + verify(mTetheringMetrics).createBuilder(eq(TETHERING_NCM), anyString()); // Change the USB tethering function to NCM. Because the USB tethering function was set to // RNDIS (the default), tethering is stopped. forceUsbTetheringUse(TETHER_USB_NCM_FUNCTION); verifyUsbTetheringStopDueToSettingChange(TEST_NCM_IFNAME); + verify(mTetheringMetrics).updateErrorCode(anyInt(), eq(TETHER_ERROR_NO_ERROR)); + verify(mTetheringMetrics).sendReport(eq(TETHERING_NCM)); // If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be // available. final ResultListener ncmResult = new ResultListener(TETHER_ERROR_SERVICE_UNAVAIL); - mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), ncmResult); + mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG, + ncmResult); mLooper.dispatchAll(); ncmResult.assertHasResult(); + verify(mTetheringMetrics, times(2)).createBuilder(eq(TETHERING_NCM), anyString()); + verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_NCM), + eq(TETHER_ERROR_SERVICE_UNAVAIL)); + verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_NCM)); // Run TETHERING_USB with ncm configuration. runDualStackUsbTethering(TEST_NCM_IFNAME); diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java new file mode 100644 index 0000000000..c34cf5f211 --- /dev/null +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2022 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.metrics; + +import static android.net.TetheringManager.TETHERING_BLUETOOTH; +import static android.net.TetheringManager.TETHERING_ETHERNET; +import static android.net.TetheringManager.TETHERING_NCM; +import static android.net.TetheringManager.TETHERING_USB; +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHERING_WIFI_P2P; +import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; +import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; +import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; +import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; +import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; +import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; +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_TYPE; +import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; +import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; + +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.stats.connectivity.DownstreamType; +import android.stats.connectivity.ErrorCode; +import android.stats.connectivity.UpstreamType; +import android.stats.connectivity.UserType; +import android.util.Pair; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class TetheringMetricsTest { + private static final String TEST_CALLER_PKG = "com.test.caller.pkg"; + private static final String SETTINGS_PKG = "com.android.settings"; + private static final String SYSTEMUI_PKG = "com.android.systemui"; + private static final String GMS_PKG = "com.google.android.gms"; + private TetheringMetrics mTetheringMetrics; + + private final NetworkTetheringReported.Builder mStatsBuilder = + NetworkTetheringReported.newBuilder(); + + private class MockTetheringMetrics extends TetheringMetrics { + @Override + public void write(final NetworkTetheringReported reported) { } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTetheringMetrics = spy(new MockTetheringMetrics()); + } + + private void runDownstreamTypesTest(final Pair... testPairs) + throws Exception { + for (Pair testPair : testPairs) { + final int type = testPair.first; + final DownstreamType expectedResult = testPair.second; + + mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG); + mTetheringMetrics.updateErrorCode(type, TETHER_ERROR_NO_ERROR); + mTetheringMetrics.sendReport(type); + NetworkTetheringReported expectedReport = + mStatsBuilder.setDownstreamType(expectedResult) + .setUserType(UserType.USER_UNKNOWN) + .setUpstreamType(UpstreamType.UT_UNKNOWN) + .setErrorCode(ErrorCode.EC_NO_ERROR) + .build(); + verify(mTetheringMetrics).write(expectedReport); + reset(mTetheringMetrics); + } + } + + @Test + public void testDownstreamTypes() throws Exception { + runDownstreamTypesTest(new Pair<>(TETHERING_WIFI, DownstreamType.DS_TETHERING_WIFI), + new Pair<>(TETHERING_WIFI_P2P, DownstreamType.DS_TETHERING_WIFI_P2P), + new Pair<>(TETHERING_BLUETOOTH, DownstreamType.DS_TETHERING_BLUETOOTH), + new Pair<>(TETHERING_USB, DownstreamType.DS_TETHERING_USB), + new Pair<>(TETHERING_NCM, DownstreamType.DS_TETHERING_NCM), + new Pair<>(TETHERING_ETHERNET, DownstreamType.DS_TETHERING_ETHERNET)); + } + + private void runErrorCodesTest(final Pair... testPairs) + throws Exception { + for (Pair testPair : testPairs) { + final int errorCode = testPair.first; + final ErrorCode expectedResult = testPair.second; + + mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG); + mTetheringMetrics.updateErrorCode(TETHERING_WIFI, errorCode); + mTetheringMetrics.sendReport(TETHERING_WIFI); + NetworkTetheringReported expectedReport = + mStatsBuilder.setDownstreamType(DownstreamType.DS_TETHERING_WIFI) + .setUserType(UserType.USER_UNKNOWN) + .setUpstreamType(UpstreamType.UT_UNKNOWN) + .setErrorCode(expectedResult) + .build(); + verify(mTetheringMetrics).write(expectedReport); + reset(mTetheringMetrics); + } + } + + @Test + public void testErrorCodes() throws Exception { + runErrorCodesTest(new Pair<>(TETHER_ERROR_NO_ERROR, ErrorCode.EC_NO_ERROR), + new Pair<>(TETHER_ERROR_UNKNOWN_IFACE, ErrorCode.EC_UNKNOWN_IFACE), + new Pair<>(TETHER_ERROR_SERVICE_UNAVAIL, ErrorCode.EC_SERVICE_UNAVAIL), + new Pair<>(TETHER_ERROR_UNSUPPORTED, ErrorCode.EC_UNSUPPORTED), + new Pair<>(TETHER_ERROR_UNAVAIL_IFACE, ErrorCode.EC_UNAVAIL_IFACE), + new Pair<>(TETHER_ERROR_INTERNAL_ERROR, ErrorCode.EC_INTERNAL_ERROR), + new Pair<>(TETHER_ERROR_TETHER_IFACE_ERROR, ErrorCode.EC_TETHER_IFACE_ERROR), + new Pair<>(TETHER_ERROR_UNTETHER_IFACE_ERROR, ErrorCode.EC_UNTETHER_IFACE_ERROR), + new Pair<>(TETHER_ERROR_ENABLE_FORWARDING_ERROR, + ErrorCode.EC_ENABLE_FORWARDING_ERROR), + new Pair<>(TETHER_ERROR_DISABLE_FORWARDING_ERROR, + ErrorCode.EC_DISABLE_FORWARDING_ERROR), + new Pair<>(TETHER_ERROR_IFACE_CFG_ERROR, ErrorCode.EC_IFACE_CFG_ERROR), + new Pair<>(TETHER_ERROR_PROVISIONING_FAILED, ErrorCode.EC_PROVISIONING_FAILED), + new Pair<>(TETHER_ERROR_DHCPSERVER_ERROR, ErrorCode.EC_DHCPSERVER_ERROR), + new Pair<>(TETHER_ERROR_ENTITLEMENT_UNKNOWN, ErrorCode.EC_ENTITLEMENT_UNKNOWN), + new Pair<>(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, + ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION), + new Pair<>(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION, + ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION), + new Pair<>(TETHER_ERROR_UNKNOWN_TYPE, ErrorCode.EC_UNKNOWN_TYPE)); + } + + private void runUserTypesTest(final Pair... testPairs) + throws Exception { + for (Pair testPair : testPairs) { + final String callerPkg = testPair.first; + final UserType expectedResult = testPair.second; + + mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg); + mTetheringMetrics.updateErrorCode(TETHERING_WIFI, TETHER_ERROR_NO_ERROR); + mTetheringMetrics.sendReport(TETHERING_WIFI); + NetworkTetheringReported expectedReport = + mStatsBuilder.setDownstreamType(DownstreamType.DS_TETHERING_WIFI) + .setUserType(expectedResult) + .setUpstreamType(UpstreamType.UT_UNKNOWN) + .setErrorCode(ErrorCode.EC_NO_ERROR) + .build(); + verify(mTetheringMetrics).write(expectedReport); + reset(mTetheringMetrics); + } + } + + @Test + public void testUserTypes() throws Exception { + runUserTypesTest(new Pair<>(TEST_CALLER_PKG, UserType.USER_UNKNOWN), + new Pair<>(SETTINGS_PKG, UserType.USER_SETTINGS), + new Pair<>(SYSTEMUI_PKG, UserType.USER_SYSTEMUI), + new Pair<>(GMS_PKG, UserType.USER_GMS)); + } +}