Implement logging of upstream event type and duration.
Bug: 263681262
Test: atest TetheringMetricsTest
Test: statsd_testdrive 303
Change-Id: I80301ee035a9814920e3ce4b9eca6bcdc59350db
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 2e71fda..4c5bf4e 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -1861,6 +1861,7 @@
mNotificationUpdater.onUpstreamCapabilitiesChanged(
(ns != null) ? ns.networkCapabilities : null);
}
+ mTetheringMetrics.maybeUpdateUpstreamType(ns);
}
protected void setUpstreamNetwork(UpstreamNetworkState ns) {
@@ -2090,6 +2091,7 @@
mNotificationUpdater.onUpstreamCapabilitiesChanged(null);
}
mBpfCoordinator.stopPolling();
+ mTetheringMetrics.cleanup();
}
private boolean updateUpstreamWanted() {
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
index ffcea4e..c181994 100644
--- a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -16,6 +16,13 @@
package com.android.networkstack.tethering.metrics;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_NCM;
@@ -39,6 +46,8 @@
import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
import android.stats.connectivity.UpstreamType;
@@ -49,6 +58,11 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.networkstack.tethering.UpstreamNetworkState;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
/**
* Collection of utilities for tethering metrics.
*
@@ -66,21 +80,58 @@
private static final String SYSTEMUI_PKG_NAME = "com.android.systemui";
private static final String GMS_PKG_NAME = "com.google.android.gms";
private final SparseArray<NetworkTetheringReported.Builder> mBuilderMap = new SparseArray<>();
+ private final SparseArray<Long> mDownstreamStartTime = new SparseArray<Long>();
+ private final ArrayList<RecordUpstreamEvent> mUpstreamEventList = new ArrayList<>();
+ private UpstreamType mCurrentUpstream = null;
+ private Long mCurrentUpStreamStartTime = 0L;
- /** Update Tethering stats about caller's package name and downstream type. */
- public void createBuilder(final int downstreamType, final String callerPkg) {
- NetworkTetheringReported.Builder statsBuilder =
- NetworkTetheringReported.newBuilder();
- statsBuilder.setDownstreamType(downstreamTypeToEnum(downstreamType))
- .setUserType(userTypeToEnum(callerPkg))
- .setUpstreamType(UpstreamType.UT_UNKNOWN)
- .setErrorCode(ErrorCode.EC_NO_ERROR)
- .setUpstreamEvents(UpstreamEvents.newBuilder())
- .setDurationMillis(0);
- mBuilderMap.put(downstreamType, statsBuilder);
+
+ /**
+ * Return the current system time in milliseconds.
+ * @return the current system time in milliseconds.
+ */
+ public long timeNow() {
+ return System.currentTimeMillis();
}
- /** Update error code of given downstreamType. */
+ private static class RecordUpstreamEvent {
+ public final long mStartTime;
+ public final long mStopTime;
+ public final UpstreamType mUpstreamType;
+
+ RecordUpstreamEvent(final long startTime, final long stopTime,
+ final UpstreamType upstream) {
+ mStartTime = startTime;
+ mStopTime = stopTime;
+ mUpstreamType = upstream;
+ }
+ }
+
+ /**
+ * Creates a |NetworkTetheringReported.Builder| object to update the tethering stats for the
+ * specified downstream type and caller's package name. Initializes the upstream events, error
+ * code, and duration to default values. Sets the start time for the downstream type in the
+ * |mDownstreamStartTime| map.
+ * @param downstreamType The type of downstream connection (e.g. Wifi, USB, Bluetooth).
+ * @param callerPkg The package name of the caller.
+ */
+ public void createBuilder(final int downstreamType, final String callerPkg) {
+ NetworkTetheringReported.Builder statsBuilder = NetworkTetheringReported.newBuilder()
+ .setDownstreamType(downstreamTypeToEnum(downstreamType))
+ .setUserType(userTypeToEnum(callerPkg))
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(ErrorCode.EC_NO_ERROR)
+ .setUpstreamEvents(UpstreamEvents.newBuilder())
+ .setDurationMillis(0);
+ mBuilderMap.put(downstreamType, statsBuilder);
+ mDownstreamStartTime.put(downstreamType, timeNow());
+ }
+
+ /**
+ * Update the error code of the given downstream type in the Tethering stats.
+ * @param downstreamType The downstream type whose error code to update.
+ * @param errCode The error code to set.
+ */
public void updateErrorCode(final int downstreamType, final int errCode) {
NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType);
if (statsBuilder == null) {
@@ -90,38 +141,155 @@
statsBuilder.setErrorCode(errorCodeToEnum(errCode));
}
- /** Remove Tethering stats.
- * If Tethering stats is ready to write then write it before removing.
+ /**
+ * Update the list of upstream types and their duration whenever the current upstream type
+ * changes.
+ * @param ns The UpstreamNetworkState object representing the current upstream network state.
+ */
+ public void maybeUpdateUpstreamType(@Nullable final UpstreamNetworkState ns) {
+ UpstreamType upstream = transportTypeToUpstreamTypeEnum(ns);
+ if (upstream.equals(mCurrentUpstream)) return;
+
+ final long newTime = timeNow();
+ if (mCurrentUpstream != null) {
+ mUpstreamEventList.add(new RecordUpstreamEvent(mCurrentUpStreamStartTime, newTime,
+ mCurrentUpstream));
+ }
+ mCurrentUpstream = upstream;
+ mCurrentUpStreamStartTime = newTime;
+ }
+
+ /**
+ * Returns the greater of two start times.
+ * @param first the first start time
+ * @param second the second start time
+ * @return the greater start time
+ */
+ private long getGreaterStartTime(long first, long second) {
+ return first > second ? first : second;
+ }
+
+ /**
+ * Updates the upstream events builder with a new upstream event.
+ * @param upstreamEventsBuilder the builder for the upstream events list
+ * @param start the start time of the upstream event
+ * @param stop the stop time of the upstream event
+ * @param upstream the type of upstream type (e.g. Wifi, Cellular, Bluetooth, ...)
+ */
+ private void updateUpstreamEvents(final UpstreamEvents.Builder upstreamEventsBuilder,
+ final long start, final long stop, @Nullable final UpstreamType upstream) {
+ final UpstreamEvent.Builder upstreamEventBuilder = UpstreamEvent.newBuilder()
+ .setUpstreamType(upstream == null ? UpstreamType.UT_NO_NETWORK : upstream)
+ .setDurationMillis(stop - start);
+ upstreamEventsBuilder.addUpstreamEvent(upstreamEventBuilder);
+ }
+
+ /**
+ * Updates the |NetworkTetheringReported.Builder| with relevant upstream events associated with
+ * the downstream event identified by the given downstream start time.
+ *
+ * This method iterates through the list of upstream events and adds any relevant events to a
+ * |UpstreamEvents.Builder|. Upstream events are considered relevant if their stop time is
+ * greater than or equal to the given downstream start time. The method also adds the last
+ * upstream event that occurred up until the current time.
+ *
+ * The resulting |UpstreamEvents.Builder| is then added to the
+ * |NetworkTetheringReported.Builder|, along with the duration of the downstream event
+ * (i.e., stop time minus downstream start time).
+ *
+ * @param statsBuilder the builder for the NetworkTetheringReported message
+ * @param downstreamStartTime the start time of the downstream event to find relevant upstream
+ * events for
+ */
+ private void updateStatsBuilderToWrite(final NetworkTetheringReported.Builder statsBuilder,
+ final long downstreamStartTime) {
+ UpstreamEvents.Builder upstreamEventsBuilder = UpstreamEvents.newBuilder();
+ for (RecordUpstreamEvent event : mUpstreamEventList) {
+ if (downstreamStartTime > event.mStopTime) continue;
+
+ final long startTime = getGreaterStartTime(downstreamStartTime, event.mStartTime);
+ // Handle completed upstream events.
+ updateUpstreamEvents(upstreamEventsBuilder, startTime, event.mStopTime,
+ event.mUpstreamType);
+ }
+ final long startTime = getGreaterStartTime(downstreamStartTime, mCurrentUpStreamStartTime);
+ final long stopTime = timeNow();
+ // Handle the last upstream event.
+ updateUpstreamEvents(upstreamEventsBuilder, startTime, stopTime, mCurrentUpstream);
+ statsBuilder.setUpstreamEvents(upstreamEventsBuilder);
+ statsBuilder.setDurationMillis(stopTime - downstreamStartTime);
+ }
+
+ /**
+ * Removes tethering statistics for the given downstream type. If there are any stats to write
+ * for the downstream event associated with the type, they are written before removing the
+ * statistics.
+ *
+ * If the given downstream type does not exist in the map, an error message is logged and the
+ * method returns without doing anything.
+ *
+ * @param downstreamType the type of downstream event to remove statistics for
*/
public void sendReport(final int downstreamType) {
- final NetworkTetheringReported.Builder statsBuilder =
- mBuilderMap.get(downstreamType);
+ final NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType);
if (statsBuilder == null) {
Log.e(TAG, "Given downstreamType does not exist, this is a bug!");
return;
}
+
+ updateStatsBuilderToWrite(statsBuilder, mDownstreamStartTime.get(downstreamType));
write(statsBuilder.build());
+
mBuilderMap.remove(downstreamType);
+ mDownstreamStartTime.remove(downstreamType);
}
- /** Collect Tethering stats and write metrics data to statsd pipeline. */
+ /**
+ * Collects tethering statistics and writes them to the statsd pipeline. This method takes in a
+ * NetworkTetheringReported object, extracts its fields and uses them to write statistics data
+ * to the statsd pipeline.
+ *
+ * @param reported a NetworkTetheringReported object containing statistics to write
+ */
@VisibleForTesting
public void write(@NonNull final NetworkTetheringReported reported) {
- TetheringStatsLog.write(TetheringStatsLog.NETWORK_TETHERING_REPORTED,
+ final byte[] upstreamEvents = reported.getUpstreamEvents().toByteArray();
+
+ TetheringStatsLog.write(
+ TetheringStatsLog.NETWORK_TETHERING_REPORTED,
reported.getErrorCode().getNumber(),
reported.getDownstreamType().getNumber(),
reported.getUpstreamType().getNumber(),
reported.getUserType().getNumber(),
- null, 0);
+ upstreamEvents,
+ reported.getDurationMillis());
if (DBG) {
- Log.d(TAG, "Write errorCode: " + reported.getErrorCode().getNumber()
- + ", downstreamType: " + reported.getDownstreamType().getNumber()
- + ", upstreamType: " + reported.getUpstreamType().getNumber()
- + ", userType: " + reported.getUserType().getNumber());
+ Log.d(
+ TAG,
+ "Write errorCode: "
+ + reported.getErrorCode().getNumber()
+ + ", downstreamType: "
+ + reported.getDownstreamType().getNumber()
+ + ", upstreamType: "
+ + reported.getUpstreamType().getNumber()
+ + ", userType: "
+ + reported.getUserType().getNumber()
+ + ", upstreamTypes: "
+ + Arrays.toString(upstreamEvents)
+ + ", durationMillis: "
+ + reported.getDurationMillis());
}
}
- /** Map {@link TetheringType} to {@link DownstreamType} */
+ /**
+ * Cleans up the variables related to upstream events when tethering is turned off.
+ */
+ public void cleanup() {
+ mUpstreamEventList.clear();
+ mCurrentUpstream = null;
+ mCurrentUpStreamStartTime = 0L;
+ }
+
private DownstreamType downstreamTypeToEnum(final int ifaceType) {
switch(ifaceType) {
case TETHERING_WIFI:
@@ -141,7 +309,6 @@
}
}
- /** Map {@link StartTetheringError} to {@link ErrorCode} */
private ErrorCode errorCodeToEnum(final int lastError) {
switch(lastError) {
case TETHER_ERROR_NO_ERROR:
@@ -181,7 +348,6 @@
}
}
- /** Map callerPkg to {@link UserType} */
private UserType userTypeToEnum(final String callerPkg) {
if (callerPkg.equals(SETTINGS_PKG_NAME)) {
return UserType.USER_SETTINGS;
@@ -193,4 +359,40 @@
return UserType.USER_UNKNOWN;
}
}
+
+ private UpstreamType transportTypeToUpstreamTypeEnum(final UpstreamNetworkState ns) {
+ final NetworkCapabilities nc = (ns != null) ? ns.networkCapabilities : null;
+ if (nc == null) return UpstreamType.UT_NO_NETWORK;
+
+ final int typeCount = nc.getTransportTypes().length;
+
+ boolean hasCellular = nc.hasTransport(TRANSPORT_CELLULAR);
+ boolean hasWifi = nc.hasTransport(TRANSPORT_WIFI);
+ boolean hasBT = nc.hasTransport(TRANSPORT_BLUETOOTH);
+ boolean hasEthernet = nc.hasTransport(TRANSPORT_ETHERNET);
+ boolean hasVpn = nc.hasTransport(TRANSPORT_VPN);
+ boolean hasWifiAware = nc.hasTransport(TRANSPORT_WIFI_AWARE);
+ boolean hasLopan = nc.hasTransport(TRANSPORT_LOWPAN);
+
+ if (typeCount == 3 && hasCellular && hasWifi && hasVpn) {
+ return UpstreamType.UT_WIFI_CELLULAR_VPN;
+ }
+
+ if (typeCount == 2 && hasVpn) {
+ if (hasCellular) return UpstreamType.UT_CELLULAR_VPN;
+ if (hasWifi) return UpstreamType.UT_WIFI_VPN;
+ if (hasBT) return UpstreamType.UT_BLUETOOTH_VPN;
+ if (hasEthernet) return UpstreamType.UT_ETHERNET_VPN;
+ }
+
+ if (typeCount == 1) {
+ if (hasCellular) return UpstreamType.UT_CELLULAR;
+ if (hasWifi) return UpstreamType.UT_WIFI;
+ if (hasBT) return UpstreamType.UT_BLUETOOTH;
+ if (hasEthernet) return UpstreamType.UT_ETHERNET;
+ if (hasWifiAware) return UpstreamType.UT_WIFI_AWARE;
+ if (hasLopan) return UpstreamType.UT_LOWPAN;
+ }
+ return UpstreamType.UT_UNKNOWN;
+ }
}
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 79590b7..3aa80c4 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -2001,6 +2001,7 @@
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
+ verify(mTetheringMetrics, times(0)).maybeUpdateUpstreamType(any());
verify(mTetheringMetrics, times(2)).updateErrorCode(eq(TETHERING_WIFI),
eq(TETHER_ERROR_INTERNAL_ERROR));
verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_WIFI));
@@ -3377,6 +3378,7 @@
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
verify(mTetheringMetrics).createBuilder(eq(TETHERING_NCM), anyString());
+ verify(mTetheringMetrics, times(1)).maybeUpdateUpstreamType(any());
// Change the USB tethering function to NCM. Because the USB tethering function was set to
// RNDIS (the default), tethering is stopped.
@@ -3393,6 +3395,7 @@
mLooper.dispatchAll();
ncmResult.assertHasResult();
verify(mTetheringMetrics, times(2)).createBuilder(eq(TETHERING_NCM), anyString());
+ verify(mTetheringMetrics, times(1)).maybeUpdateUpstreamType(any());
verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_NCM),
eq(TETHER_ERROR_SERVICE_UNAVAIL));
verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_NCM));
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
index 98c873d..a570736 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -16,6 +16,13 @@
package com.android.networkstack.tethering.metrics;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_NCM;
@@ -44,6 +51,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.net.NetworkCapabilities;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
import android.stats.connectivity.UpstreamType;
@@ -52,6 +60,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.networkstack.tethering.UpstreamNetworkState;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -64,46 +74,105 @@
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 static final long TEST_START_TIME = 1670395936033L;
+ private static final long SECOND_IN_MILLIS = 1_000L;
+ private TetheringMetrics mTetheringMetrics;
private final NetworkTetheringReported.Builder mStatsBuilder =
NetworkTetheringReported.newBuilder();
+ private long mElapsedRealtime;
+
private class MockTetheringMetrics extends TetheringMetrics {
@Override
- public void write(final NetworkTetheringReported reported) { }
+ public void write(final NetworkTetheringReported reported) {}
+ @Override
+ public long timeNow() {
+ return currentTimeMillis();
+ }
+ }
+
+ private long currentTimeMillis() {
+ return TEST_START_TIME + mElapsedRealtime;
+ }
+
+ private void incrementCurrentTime(final long duration) {
+ mElapsedRealtime += duration;
+ mTetheringMetrics.timeNow();
+ }
+
+ private long getElapsedRealtime() {
+ return mElapsedRealtime;
+ }
+
+ private void clearElapsedRealtime() {
+ mElapsedRealtime = 0;
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTetheringMetrics = spy(new MockTetheringMetrics());
+ mElapsedRealtime = 0L;
}
- private void verifyReport(DownstreamType downstream, ErrorCode error, UserType user)
+ private void verifyReport(final DownstreamType downstream, final ErrorCode error,
+ final UserType user, final UpstreamEvents.Builder upstreamEvents, final long duration)
throws Exception {
final NetworkTetheringReported expectedReport =
mStatsBuilder.setDownstreamType(downstream)
.setUserType(user)
.setUpstreamType(UpstreamType.UT_UNKNOWN)
.setErrorCode(error)
- .setUpstreamEvents(UpstreamEvents.newBuilder())
- .setDurationMillis(0)
+ .setUpstreamEvents(upstreamEvents)
+ .setDurationMillis(duration)
.build();
verify(mTetheringMetrics).write(expectedReport);
}
- private void updateErrorAndSendReport(int downstream, int error) {
+ private void updateErrorAndSendReport(final int downstream, final int error) {
mTetheringMetrics.updateErrorCode(downstream, error);
mTetheringMetrics.sendReport(downstream);
}
+ private static NetworkCapabilities buildUpstreamCapabilities(final int[] transports) {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ for (int type: transports) {
+ nc.addTransportType(type);
+ }
+ return nc;
+ }
+
+ private static UpstreamNetworkState buildUpstreamState(final int... transports) {
+ return new UpstreamNetworkState(
+ null,
+ buildUpstreamCapabilities(transports),
+ null);
+ }
+
+ private void addUpstreamEvent(UpstreamEvents.Builder upstreamEvents,
+ final UpstreamType expectedResult, final long duration) {
+ UpstreamEvent.Builder upstreamEvent = UpstreamEvent.newBuilder()
+ .setUpstreamType(expectedResult)
+ .setDurationMillis(duration);
+ upstreamEvents.addUpstreamEvent(upstreamEvent);
+ }
+
private void runDownstreamTypesTest(final int type, final DownstreamType expectedResult)
throws Exception {
mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG);
+ final long duration = 2 * SECOND_IN_MILLIS;
+ incrementCurrentTime(duration);
+ UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder();
+ // Set UpstreamType as NO_NETWORK because the upstream type has not been changed.
+ addUpstreamEvent(upstreamEvents, UpstreamType.UT_NO_NETWORK, duration);
updateErrorAndSendReport(type, TETHER_ERROR_NO_ERROR);
- verifyReport(expectedResult, ErrorCode.EC_NO_ERROR, UserType.USER_UNKNOWN);
+
+ verifyReport(expectedResult, ErrorCode.EC_NO_ERROR, UserType.USER_UNKNOWN,
+ upstreamEvents, getElapsedRealtime());
reset(mTetheringMetrics);
+ clearElapsedRealtime();
+ mTetheringMetrics.cleanup();
}
@Test
@@ -119,8 +188,18 @@
private void runErrorCodesTest(final int errorCode, final ErrorCode expectedResult)
throws Exception {
mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
+ mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI));
+ final long duration = 2 * SECOND_IN_MILLIS;
+ incrementCurrentTime(duration);
updateErrorAndSendReport(TETHERING_WIFI, errorCode);
- verifyReport(DownstreamType.DS_TETHERING_WIFI, expectedResult, UserType.USER_UNKNOWN);
+
+ UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder();
+ addUpstreamEvent(upstreamEvents, UpstreamType.UT_WIFI, duration);
+ verifyReport(DownstreamType.DS_TETHERING_WIFI, expectedResult, UserType.USER_UNKNOWN,
+ upstreamEvents, getElapsedRealtime());
+ reset(mTetheringMetrics);
+ clearElapsedRealtime();
+ mTetheringMetrics.cleanup();
}
@Test
@@ -151,9 +230,18 @@
private void runUserTypesTest(final String callerPkg, final UserType expectedResult)
throws Exception {
mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg);
+ final long duration = 1 * SECOND_IN_MILLIS;
+ incrementCurrentTime(duration);
updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
- verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, expectedResult);
+
+ UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder();
+ // Set UpstreamType as NO_NETWORK because the upstream type has not been changed.
+ addUpstreamEvent(upstreamEvents, UpstreamType.UT_NO_NETWORK, duration);
+ verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, expectedResult,
+ upstreamEvents, getElapsedRealtime());
reset(mTetheringMetrics);
+ clearElapsedRealtime();
+ mTetheringMetrics.cleanup();
}
@Test
@@ -164,22 +252,113 @@
runUserTypesTest(GMS_PKG, UserType.USER_GMS);
}
+ private void runUpstreamTypesTest(final UpstreamNetworkState ns,
+ final UpstreamType expectedResult) throws Exception {
+ mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
+ mTetheringMetrics.maybeUpdateUpstreamType(ns);
+ final long duration = 2 * SECOND_IN_MILLIS;
+ incrementCurrentTime(duration);
+ updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
+
+ UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder();
+ addUpstreamEvent(upstreamEvents, expectedResult, duration);
+ verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR,
+ UserType.USER_UNKNOWN, upstreamEvents, getElapsedRealtime());
+ reset(mTetheringMetrics);
+ clearElapsedRealtime();
+ mTetheringMetrics.cleanup();
+ }
+
+ @Test
+ public void testUpstreamTypes() throws Exception {
+ runUpstreamTypesTest(null , UpstreamType.UT_NO_NETWORK);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_CELLULAR), UpstreamType.UT_CELLULAR);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_WIFI), UpstreamType.UT_WIFI);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_BLUETOOTH), UpstreamType.UT_BLUETOOTH);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_ETHERNET), UpstreamType.UT_ETHERNET);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_WIFI_AWARE), UpstreamType.UT_WIFI_AWARE);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_LOWPAN), UpstreamType.UT_LOWPAN);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_CELLULAR, TRANSPORT_VPN),
+ UpstreamType.UT_CELLULAR_VPN);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_WIFI, TRANSPORT_VPN),
+ UpstreamType.UT_WIFI_VPN);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_BLUETOOTH, TRANSPORT_VPN),
+ UpstreamType.UT_BLUETOOTH_VPN);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_ETHERNET, TRANSPORT_VPN),
+ UpstreamType.UT_ETHERNET_VPN);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_CELLULAR, TRANSPORT_WIFI, TRANSPORT_VPN),
+ UpstreamType.UT_WIFI_CELLULAR_VPN);
+ runUpstreamTypesTest(buildUpstreamState(TRANSPORT_CELLULAR, TRANSPORT_WIFI, TRANSPORT_VPN,
+ TRANSPORT_BLUETOOTH), UpstreamType.UT_UNKNOWN);
+ }
+
@Test
public void testMultiBuildersCreatedBeforeSendReport() throws Exception {
mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG);
+ final long wifiTetheringStartTime = currentTimeMillis();
+ incrementCurrentTime(1 * SECOND_IN_MILLIS);
mTetheringMetrics.createBuilder(TETHERING_USB, SYSTEMUI_PKG);
+ final long usbTetheringStartTime = currentTimeMillis();
+ incrementCurrentTime(2 * SECOND_IN_MILLIS);
mTetheringMetrics.createBuilder(TETHERING_BLUETOOTH, GMS_PKG);
-
+ final long bluetoothTetheringStartTime = currentTimeMillis();
+ incrementCurrentTime(3 * SECOND_IN_MILLIS);
updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_DHCPSERVER_ERROR);
+
+ UpstreamEvents.Builder wifiTetheringUpstreamEvents = UpstreamEvents.newBuilder();
+ addUpstreamEvent(wifiTetheringUpstreamEvents, UpstreamType.UT_NO_NETWORK,
+ currentTimeMillis() - wifiTetheringStartTime);
verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_DHCPSERVER_ERROR,
- UserType.USER_SETTINGS);
-
+ UserType.USER_SETTINGS, wifiTetheringUpstreamEvents,
+ currentTimeMillis() - wifiTetheringStartTime);
+ incrementCurrentTime(1 * SECOND_IN_MILLIS);
updateErrorAndSendReport(TETHERING_USB, TETHER_ERROR_ENABLE_FORWARDING_ERROR);
- verifyReport(DownstreamType.DS_TETHERING_USB, ErrorCode.EC_ENABLE_FORWARDING_ERROR,
- UserType.USER_SYSTEMUI);
+ UpstreamEvents.Builder usbTetheringUpstreamEvents = UpstreamEvents.newBuilder();
+ addUpstreamEvent(usbTetheringUpstreamEvents, UpstreamType.UT_NO_NETWORK,
+ currentTimeMillis() - usbTetheringStartTime);
+
+ verifyReport(DownstreamType.DS_TETHERING_USB, ErrorCode.EC_ENABLE_FORWARDING_ERROR,
+ UserType.USER_SYSTEMUI, usbTetheringUpstreamEvents,
+ currentTimeMillis() - usbTetheringStartTime);
+ incrementCurrentTime(1 * SECOND_IN_MILLIS);
updateErrorAndSendReport(TETHERING_BLUETOOTH, TETHER_ERROR_TETHER_IFACE_ERROR);
+
+ UpstreamEvents.Builder bluetoothTetheringUpstreamEvents = UpstreamEvents.newBuilder();
+ addUpstreamEvent(bluetoothTetheringUpstreamEvents, UpstreamType.UT_NO_NETWORK,
+ currentTimeMillis() - bluetoothTetheringStartTime);
verifyReport(DownstreamType.DS_TETHERING_BLUETOOTH, ErrorCode.EC_TETHER_IFACE_ERROR,
- UserType.USER_GMS);
+ UserType.USER_GMS, bluetoothTetheringUpstreamEvents,
+ currentTimeMillis() - bluetoothTetheringStartTime);
+ }
+
+ @Test
+ public void testUpstreamsWithMultipleDownstreams() throws Exception {
+ mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG);
+ final long wifiTetheringStartTime = currentTimeMillis();
+ incrementCurrentTime(1 * SECOND_IN_MILLIS);
+ mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI));
+ final long wifiUpstreamStartTime = currentTimeMillis();
+ incrementCurrentTime(5 * SECOND_IN_MILLIS);
+ mTetheringMetrics.createBuilder(TETHERING_USB, SYSTEMUI_PKG);
+ final long usbTetheringStartTime = currentTimeMillis();
+ incrementCurrentTime(5 * SECOND_IN_MILLIS);
+ updateErrorAndSendReport(TETHERING_USB, TETHER_ERROR_NO_ERROR);
+
+ UpstreamEvents.Builder usbTetheringUpstreamEvents = UpstreamEvents.newBuilder();
+ addUpstreamEvent(usbTetheringUpstreamEvents, UpstreamType.UT_WIFI,
+ currentTimeMillis() - usbTetheringStartTime);
+ verifyReport(DownstreamType.DS_TETHERING_USB, ErrorCode.EC_NO_ERROR,
+ UserType.USER_SYSTEMUI, usbTetheringUpstreamEvents,
+ currentTimeMillis() - usbTetheringStartTime);
+ incrementCurrentTime(7 * SECOND_IN_MILLIS);
+ updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
+
+ UpstreamEvents.Builder wifiTetheringUpstreamEvents = UpstreamEvents.newBuilder();
+ addUpstreamEvent(wifiTetheringUpstreamEvents, UpstreamType.UT_WIFI,
+ currentTimeMillis() - wifiUpstreamStartTime);
+ verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR,
+ UserType.USER_SETTINGS, wifiTetheringUpstreamEvents,
+ currentTimeMillis() - wifiTetheringStartTime);
}
}