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);
     }
 }