diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index d62f18f87b..13f4f2a1c1 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -2082,6 +2082,7 @@ public class Tethering { chooseUpstreamType(true); mTryCell = false; } + mTetheringMetrics.initUpstreamUsageBaseline(); } @Override diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java index 2202106b30..fc50faf88e 100644 --- a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java +++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java @@ -16,6 +16,8 @@ package com.android.networkstack.tethering.metrics; +import static android.app.usage.NetworkStats.Bucket.STATE_ALL; +import static android.app.usage.NetworkStats.Bucket.TAG_NONE; import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; @@ -24,6 +26,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static android.net.NetworkStats.DEFAULT_NETWORK_YES; import static android.net.NetworkStats.METERED_YES; +import static android.net.NetworkStats.UID_TETHERING; import static android.net.NetworkTemplate.MATCH_BLUETOOTH; import static android.net.NetworkTemplate.MATCH_ETHERNET; import static android.net.NetworkTemplate.MATCH_MOBILE; @@ -52,13 +55,19 @@ import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; import android.annotation.Nullable; +import android.app.usage.NetworkStats; +import android.app.usage.NetworkStatsManager; import android.content.Context; import android.net.NetworkCapabilities; import android.net.NetworkTemplate; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; import android.stats.connectivity.DownstreamType; import android.stats.connectivity.ErrorCode; import android.stats.connectivity.UpstreamType; import android.stats.connectivity.UserType; +import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; @@ -75,6 +84,10 @@ import java.util.Arrays; /** * Collection of utilities for tethering metrics. * + *

This class is thread-safe. All accesses to this class will be either posting to the internal + * handler thread for processing or checking whether the access is from the internal handler + * thread. However, the constructor is an exception, as it is called on another thread. + * * To see if the logs are properly sent to statsd, execute following commands * * $ adb shell cmd stats print-logs @@ -93,11 +106,16 @@ public class TetheringMetrics { */ private static final String TETHER_UPSTREAM_DATA_USAGE_METRICS = "tether_upstream_data_usage_metrics"; + @VisibleForTesting + static final DataUsage EMPTY = new DataUsage(0L /* txBytes */, 0L /* rxBytes */); private final SparseArray mBuilderMap = new SparseArray<>(); private final SparseArray mDownstreamStartTime = new SparseArray(); private final ArrayList mUpstreamEventList = new ArrayList<>(); + private final ArrayMap mUpstreamUsageBaseline = new ArrayMap<>(); private final Context mContext; private final Dependencies mDependencies; + private final NetworkStatsManager mNetworkStatsManager; + private final Handler mHandler; private UpstreamType mCurrentUpstream = null; private Long mCurrentUpStreamStartTime = 0L; @@ -136,6 +154,14 @@ public class TetheringMetrics { return SdkLevel.isAtLeastT() && DeviceConfigUtils.isTetheringFeatureNotChickenedOut( context, TETHER_UPSTREAM_DATA_USAGE_METRICS); } + + /** + * @see Handler + */ + @NonNull + public Handler createHandler(Looper looper) { + return new Handler(looper); + } } /** @@ -150,24 +176,49 @@ public class TetheringMetrics { TetheringMetrics(Context context, Dependencies dependencies) { mContext = context; mDependencies = dependencies; + mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class); + final HandlerThread thread = new HandlerThread(TAG); + thread.start(); + mHandler = dependencies.createHandler(thread.getLooper()); } - private static class DataUsage { - final long mTxBytes; - final long mRxBytes; + @VisibleForTesting + static class DataUsage { + public final long txBytes; + public final long rxBytes; DataUsage(long txBytes, long rxBytes) { - mTxBytes = txBytes; - mRxBytes = rxBytes; + this.txBytes = txBytes; + this.rxBytes = rxBytes; } - public long getTxBytes() { - return mTxBytes; + /*** Calculate the data usage delta from give new and old usage */ + public static DataUsage subtract(DataUsage newUsage, DataUsage oldUsage) { + return new DataUsage( + newUsage.txBytes - oldUsage.txBytes, + newUsage.rxBytes - oldUsage.rxBytes); } - public long getRxBytes() { - return mRxBytes; + @Override + public int hashCode() { + return (int) (txBytes & 0xFFFFFFFF) + + ((int) (txBytes >> 32) * 3) + + ((int) (rxBytes & 0xFFFFFFFF) * 5) + + ((int) (rxBytes >> 32) * 7); } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof DataUsage)) { + return false; + } + return txBytes == ((DataUsage) other).txBytes + && rxBytes == ((DataUsage) other).rxBytes; + } + } private static class RecordUpstreamEvent { @@ -194,6 +245,10 @@ public class TetheringMetrics { * @param callerPkg The package name of the caller. */ public void createBuilder(final int downstreamType, final String callerPkg) { + mHandler.post(() -> handleCreateBuilder(downstreamType, callerPkg)); + } + + private void handleCreateBuilder(final int downstreamType, final String callerPkg) { NetworkTetheringReported.Builder statsBuilder = NetworkTetheringReported.newBuilder() .setDownstreamType(downstreamTypeToEnum(downstreamType)) .setUserType(userTypeToEnum(callerPkg)) @@ -211,6 +266,10 @@ public class TetheringMetrics { * @param errCode The error code to set. */ public void updateErrorCode(final int downstreamType, final int errCode) { + mHandler.post(() -> handleUpdateErrorCode(downstreamType, errCode)); + } + + private void handleUpdateErrorCode(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!"); @@ -219,13 +278,26 @@ public class TetheringMetrics { statsBuilder.setErrorCode(errorCodeToEnum(errCode)); } - private DataUsage calculateDataUsage(@Nullable UpstreamType upstream) { + /** + * Calculates the data usage difference between the current and previous usage for the + * specified upstream type. + * + * @return A DataUsage object containing the calculated difference in transmitted (tx) and + * received (rx) bytes. + */ + private DataUsage calculateDataUsageDelta(@Nullable UpstreamType upstream) { if (upstream != null && mDependencies.isUpstreamDataUsageMetricsEnabled(mContext) && isUsageSupportedForUpstreamType(upstream)) { - // TODO: Implement data usage calculation for the upstream type. - return new DataUsage(0L, 0L); + final DataUsage oldUsage = mUpstreamUsageBaseline.getOrDefault(upstream, EMPTY); + if (oldUsage.equals(EMPTY)) { + Log.d(TAG, "No usage baseline for the upstream=" + upstream); + return EMPTY; + } + // TODO(b/352537247): Fix data usage which might be incorrect if the device uses + // tethering with the same upstream for over 15 days. + return DataUsage.subtract(getCurrentDataUsageForUpstreamType(upstream), oldUsage); } - return new DataUsage(0L, 0L); + return EMPTY; } /** @@ -234,12 +306,16 @@ public class TetheringMetrics { * @param ns The UpstreamNetworkState object representing the current upstream network state. */ public void maybeUpdateUpstreamType(@Nullable final UpstreamNetworkState ns) { + mHandler.post(() -> handleMaybeUpdateUpstreamType(ns)); + } + + private void handleMaybeUpdateUpstreamType(@Nullable final UpstreamNetworkState ns) { UpstreamType upstream = transportTypeToUpstreamTypeEnum(ns); if (upstream.equals(mCurrentUpstream)) return; final long newTime = mDependencies.timeNow(); if (mCurrentUpstream != null) { - final DataUsage dataUsage = calculateDataUsage(upstream); + final DataUsage dataUsage = calculateDataUsageDelta(mCurrentUpstream); mUpstreamEventList.add(new RecordUpstreamEvent(mCurrentUpStreamStartTime, newTime, mCurrentUpstream, dataUsage)); } @@ -292,14 +368,14 @@ public class TetheringMetrics { final long startTime = Math.max(downstreamStartTime, event.mStartTime); // Handle completed upstream events. addUpstreamEvent(upstreamEventsBuilder, startTime, event.mStopTime, - event.mUpstreamType, event.mDataUsage.mTxBytes, event.mDataUsage.mRxBytes); + event.mUpstreamType, event.mDataUsage.txBytes, event.mDataUsage.rxBytes); } final long startTime = Math.max(downstreamStartTime, mCurrentUpStreamStartTime); final long stopTime = mDependencies.timeNow(); // Handle the last upstream event. - final DataUsage dataUsage = calculateDataUsage(mCurrentUpstream); + final DataUsage dataUsage = calculateDataUsageDelta(mCurrentUpstream); addUpstreamEvent(upstreamEventsBuilder, startTime, stopTime, mCurrentUpstream, - dataUsage.mTxBytes, dataUsage.mRxBytes); + dataUsage.txBytes, dataUsage.rxBytes); statsBuilder.setUpstreamEvents(upstreamEventsBuilder); statsBuilder.setDurationMillis(stopTime - downstreamStartTime); } @@ -315,6 +391,10 @@ public class TetheringMetrics { * @param downstreamType the type of downstream event to remove statistics for */ public void sendReport(final int downstreamType) { + mHandler.post(() -> handleSendReport(downstreamType)); + } + + private void handleSendReport(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!"); @@ -335,8 +415,7 @@ public class TetheringMetrics { * * @param reported a NetworkTetheringReported object containing statistics to write */ - @VisibleForTesting - public void write(@NonNull final NetworkTetheringReported reported) { + private void write(@NonNull final NetworkTetheringReported reported) { final byte[] upstreamEvents = reported.getUpstreamEvents().toByteArray(); mDependencies.write(reported); if (DBG) { @@ -357,13 +436,68 @@ public class TetheringMetrics { } } + /** + * Initialize the upstream data usage baseline when tethering is turned on. + */ + public void initUpstreamUsageBaseline() { + mHandler.post(() -> handleInitUpstreamUsageBaseline()); + } + + private void handleInitUpstreamUsageBaseline() { + if (!(mDependencies.isUpstreamDataUsageMetricsEnabled(mContext) + && mUpstreamUsageBaseline.isEmpty())) { + return; + } + + for (UpstreamType type : UpstreamType.values()) { + if (!isUsageSupportedForUpstreamType(type)) continue; + mUpstreamUsageBaseline.put(type, getCurrentDataUsageForUpstreamType(type)); + } + } + + @VisibleForTesting + @NonNull + DataUsage getDataUsageFromUpstreamType(@NonNull UpstreamType type) { + if (mHandler.getLooper().getThread() != Thread.currentThread()) { + throw new IllegalStateException( + "Not running on Handler thread: " + Thread.currentThread().getName()); + } + return mUpstreamUsageBaseline.getOrDefault(type, EMPTY); + } + + + /** + * Get the current usage for given upstream type. + */ + @NonNull + private DataUsage getCurrentDataUsageForUpstreamType(@NonNull UpstreamType type) { + final NetworkStats stats = mNetworkStatsManager.queryDetailsForUidTagState( + buildNetworkTemplateForUpstreamType(type), Long.MIN_VALUE, Long.MAX_VALUE, + UID_TETHERING, TAG_NONE, STATE_ALL); + + final NetworkStats.Bucket bucket = new NetworkStats.Bucket(); + Long totalTxBytes = 0L; + Long totalRxBytes = 0L; + while (stats.hasNextBucket()) { + stats.getNextBucket(bucket); + totalTxBytes += bucket.getTxBytes(); + totalRxBytes += bucket.getRxBytes(); + } + return new DataUsage(totalTxBytes, totalRxBytes); + } + /** * Cleans up the variables related to upstream events when tethering is turned off. */ public void cleanup() { + mHandler.post(() -> handleCleanup()); + } + + private void handleCleanup() { mUpstreamEventList.clear(); mCurrentUpstream = null; mCurrentUpStreamStartTime = 0L; + mUpstreamUsageBaseline.clear(); } private DownstreamType downstreamTypeToEnum(final int ifaceType) { 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 fbc2893803..34689bcd54 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,12 +16,19 @@ package com.android.networkstack.tethering.metrics; +import static android.app.usage.NetworkStats.Bucket.STATE_ALL; +import static android.app.usage.NetworkStats.Bucket.TAG_NONE; 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_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; +import static android.net.NetworkStats.DEFAULT_NETWORK_YES; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.UID_TETHERING; import static android.net.NetworkTemplate.MATCH_BLUETOOTH; import static android.net.NetworkTemplate.MATCH_ETHERNET; import static android.net.NetworkTemplate.MATCH_MOBILE; @@ -49,29 +56,47 @@ 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 android.stats.connectivity.UpstreamType.UT_BLUETOOTH; +import static android.stats.connectivity.UpstreamType.UT_CELLULAR; +import static android.stats.connectivity.UpstreamType.UT_ETHERNET; +import static android.stats.connectivity.UpstreamType.UT_WIFI; + +import static com.android.networkstack.tethering.metrics.TetheringMetrics.EMPTY; +import static com.android.testutils.NetworkStatsUtilsKt.makePublicStatsFromAndroidNetStats; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; +import android.app.usage.NetworkStatsManager; import android.content.Context; import android.net.NetworkCapabilities; +import android.net.NetworkStats; import android.net.NetworkTemplate; import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; import android.stats.connectivity.DownstreamType; import android.stats.connectivity.ErrorCode; import android.stats.connectivity.UpstreamType; import android.stats.connectivity.UserType; +import android.util.ArrayMap; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.networkstack.tethering.UpstreamNetworkState; +import com.android.networkstack.tethering.metrics.TetheringMetrics.DataUsage; import com.android.networkstack.tethering.metrics.TetheringMetrics.Dependencies; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; +import com.android.testutils.HandlerUtils; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -90,14 +115,19 @@ public final class TetheringMetricsTest { private static final String GMS_PKG = "com.google.android.gms"; private static final long TEST_START_TIME = 1670395936033L; private static final long SECOND_IN_MILLIS = 1_000L; + private static final long DEFAULT_TIMEOUT = 2000L; private static final int MATCH_NONE = -1; @Mock private Context mContext; @Mock private Dependencies mDeps; + @Mock private NetworkStatsManager mNetworkStatsManager; private TetheringMetrics mTetheringMetrics; private final NetworkTetheringReported.Builder mStatsBuilder = NetworkTetheringReported.newBuilder(); + private final ArrayMap mMockUpstreamUsageBaseline = new ArrayMap<>(); + private HandlerThread mThread; + private Handler mHandler; private long mElapsedRealtime; @@ -124,10 +154,35 @@ public final class TetheringMetricsTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); doReturn(TEST_START_TIME).when(mDeps).timeNow(); + doReturn(mNetworkStatsManager).when(mContext).getSystemService(NetworkStatsManager.class); + mThread = new HandlerThread("TetheringMetricsTest"); + mThread.start(); + mHandler = new Handler(mThread.getLooper()); + doReturn(mHandler).when(mDeps).createHandler(any()); + // Set up the usage for upstream types. + mMockUpstreamUsageBaseline.put(UT_CELLULAR, new DataUsage(100L, 200L)); + mMockUpstreamUsageBaseline.put(UT_WIFI, new DataUsage(400L, 800L)); + mMockUpstreamUsageBaseline.put(UT_BLUETOOTH, new DataUsage(50L, 80L)); + mMockUpstreamUsageBaseline.put(UT_ETHERNET, new DataUsage(0L, 0L)); + doAnswer(inv -> { + final NetworkTemplate template = (NetworkTemplate) inv.getArguments()[0]; + final DataUsage dataUsage = mMockUpstreamUsageBaseline.getOrDefault( + matchRuleToUpstreamType(template.getMatchRule()), new DataUsage(0L, 0L)); + return makeNetworkStatsWithTxRxBytes(dataUsage); + }).when(mNetworkStatsManager).queryDetailsForUidTagState(any(), eq(Long.MIN_VALUE), + eq(Long.MAX_VALUE), eq(UID_TETHERING), eq(TAG_NONE), eq(STATE_ALL)); mTetheringMetrics = new TetheringMetrics(mContext, mDeps); mElapsedRealtime = 0L; } + @After + public void tearDown() throws Exception { + if (mThread != null) { + mThread.quitSafely(); + mThread.join(); + } + } + private void verifyReport(final DownstreamType downstream, final ErrorCode error, final UserType user, final UpstreamEvents.Builder upstreamEvents, final long duration) throws Exception { @@ -142,9 +197,15 @@ public final class TetheringMetricsTest { verify(mDeps).write(expectedReport); } + private void runAndWaitForIdle(Runnable r) { + r.run(); + HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT); + } + private void updateErrorAndSendReport(final int downstream, final int error) { mTetheringMetrics.updateErrorCode(downstream, error); mTetheringMetrics.sendReport(downstream); + HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT); } private static NetworkCapabilities buildUpstreamCapabilities(final int[] transports) { @@ -176,7 +237,7 @@ public final class TetheringMetricsTest { private void runDownstreamTypesTest(final int type, final DownstreamType expectedResult) throws Exception { mTetheringMetrics = new TetheringMetrics(mContext, mDeps); - mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG); + runAndWaitForIdle(() -> mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG)); final long duration = 2 * SECOND_IN_MILLIS; incrementCurrentTime(duration); UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder(); @@ -202,14 +263,15 @@ public final class TetheringMetricsTest { private void runErrorCodesTest(final int errorCode, final ErrorCode expectedResult) throws Exception { mTetheringMetrics = new TetheringMetrics(mContext, mDeps); - mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG); - mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI)); + runAndWaitForIdle(() -> mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG)); + runAndWaitForIdle(() -> + mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI))); final long duration = 2 * SECOND_IN_MILLIS; incrementCurrentTime(duration); updateErrorAndSendReport(TETHERING_WIFI, errorCode); UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder(); - addUpstreamEvent(upstreamEvents, UpstreamType.UT_WIFI, duration, 0L, 0L); + addUpstreamEvent(upstreamEvents, UT_WIFI, duration, 0L, 0L); verifyReport(DownstreamType.DS_TETHERING_WIFI, expectedResult, UserType.USER_UNKNOWN, upstreamEvents, getElapsedRealtime()); clearElapsedRealtime(); @@ -243,7 +305,7 @@ public final class TetheringMetricsTest { private void runUserTypesTest(final String callerPkg, final UserType expectedResult) throws Exception { mTetheringMetrics = new TetheringMetrics(mContext, mDeps); - mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg); + runAndWaitForIdle(() -> mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg)); final long duration = 1 * SECOND_IN_MILLIS; incrementCurrentTime(duration); updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR); @@ -267,8 +329,8 @@ public final class TetheringMetricsTest { private void runUpstreamTypesTest(final UpstreamNetworkState ns, final UpstreamType expectedResult) throws Exception { mTetheringMetrics = new TetheringMetrics(mContext, mDeps); - mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG); - mTetheringMetrics.maybeUpdateUpstreamType(ns); + runAndWaitForIdle(() -> mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG)); + runAndWaitForIdle(() -> mTetheringMetrics.maybeUpdateUpstreamType(ns)); final long duration = 2 * SECOND_IN_MILLIS; incrementCurrentTime(duration); updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR); @@ -283,10 +345,10 @@ public final class TetheringMetricsTest { @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_CELLULAR), UT_CELLULAR); + runUpstreamTypesTest(buildUpstreamState(TRANSPORT_WIFI), UT_WIFI); + runUpstreamTypesTest(buildUpstreamState(TRANSPORT_BLUETOOTH), UT_BLUETOOTH); + runUpstreamTypesTest(buildUpstreamState(TRANSPORT_ETHERNET), UT_ETHERNET); runUpstreamTypesTest(buildUpstreamState(TRANSPORT_WIFI_AWARE), UpstreamType.UT_WIFI_AWARE); runUpstreamTypesTest(buildUpstreamState(TRANSPORT_LOWPAN), UpstreamType.UT_LOWPAN); runUpstreamTypesTest(buildUpstreamState(TRANSPORT_CELLULAR, TRANSPORT_WIFI, @@ -295,13 +357,13 @@ public final class TetheringMetricsTest { @Test public void testMultiBuildersCreatedBeforeSendReport() throws Exception { - mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG); + runAndWaitForIdle(() -> mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG)); final long wifiTetheringStartTime = currentTimeMillis(); incrementCurrentTime(1 * SECOND_IN_MILLIS); - mTetheringMetrics.createBuilder(TETHERING_USB, SYSTEMUI_PKG); + runAndWaitForIdle(() -> mTetheringMetrics.createBuilder(TETHERING_USB, SYSTEMUI_PKG)); final long usbTetheringStartTime = currentTimeMillis(); incrementCurrentTime(2 * SECOND_IN_MILLIS); - mTetheringMetrics.createBuilder(TETHERING_BLUETOOTH, GMS_PKG); + runAndWaitForIdle(() -> mTetheringMetrics.createBuilder(TETHERING_BLUETOOTH, GMS_PKG)); final long bluetoothTetheringStartTime = currentTimeMillis(); incrementCurrentTime(3 * SECOND_IN_MILLIS); updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_DHCPSERVER_ERROR); @@ -335,19 +397,20 @@ public final class TetheringMetricsTest { @Test public void testUpstreamsWithMultipleDownstreams() throws Exception { - mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG); + runAndWaitForIdle(() -> mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG)); final long wifiTetheringStartTime = currentTimeMillis(); incrementCurrentTime(1 * SECOND_IN_MILLIS); - mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI)); + runAndWaitForIdle(() -> + mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI))); final long wifiUpstreamStartTime = currentTimeMillis(); incrementCurrentTime(5 * SECOND_IN_MILLIS); - mTetheringMetrics.createBuilder(TETHERING_USB, SYSTEMUI_PKG); + runAndWaitForIdle(() -> 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, + addUpstreamEvent(usbTetheringUpstreamEvents, UT_WIFI, currentTimeMillis() - usbTetheringStartTime, 0L, 0L); verifyReport(DownstreamType.DS_TETHERING_USB, ErrorCode.EC_NO_ERROR, UserType.USER_SYSTEMUI, usbTetheringUpstreamEvents, @@ -356,7 +419,7 @@ public final class TetheringMetricsTest { updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR); UpstreamEvents.Builder wifiTetheringUpstreamEvents = UpstreamEvents.newBuilder(); - addUpstreamEvent(wifiTetheringUpstreamEvents, UpstreamType.UT_WIFI, + addUpstreamEvent(wifiTetheringUpstreamEvents, UT_WIFI, currentTimeMillis() - wifiUpstreamStartTime, 0L, 0L); verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, UserType.USER_SETTINGS, wifiTetheringUpstreamEvents, @@ -365,24 +428,27 @@ public final class TetheringMetricsTest { @Test public void testSwitchingMultiUpstreams() throws Exception { - mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG); + runAndWaitForIdle(() -> mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG)); final long wifiTetheringStartTime = currentTimeMillis(); incrementCurrentTime(1 * SECOND_IN_MILLIS); - mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI)); + runAndWaitForIdle(() -> + mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI))); final long wifiDuration = 5 * SECOND_IN_MILLIS; incrementCurrentTime(wifiDuration); - mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_BLUETOOTH)); + runAndWaitForIdle(() -> + mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_BLUETOOTH))); final long bluetoothDuration = 15 * SECOND_IN_MILLIS; incrementCurrentTime(bluetoothDuration); - mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_CELLULAR)); + runAndWaitForIdle(() -> + mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_CELLULAR))); final long celltoothDuration = 20 * SECOND_IN_MILLIS; incrementCurrentTime(celltoothDuration); updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR); UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder(); - addUpstreamEvent(upstreamEvents, UpstreamType.UT_WIFI, wifiDuration, 0L, 0L); - addUpstreamEvent(upstreamEvents, UpstreamType.UT_BLUETOOTH, bluetoothDuration, 0L, 0L); - addUpstreamEvent(upstreamEvents, UpstreamType.UT_CELLULAR, celltoothDuration, 0L, 0L); + addUpstreamEvent(upstreamEvents, UT_WIFI, wifiDuration, 0L, 0L); + addUpstreamEvent(upstreamEvents, UT_BLUETOOTH, bluetoothDuration, 0L, 0L); + addUpstreamEvent(upstreamEvents, UT_CELLULAR, celltoothDuration, 0L, 0L); verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, UserType.USER_SETTINGS, upstreamEvents, @@ -397,10 +463,10 @@ public final class TetheringMetricsTest { @Test public void testUsageSupportedForUpstreamTypeTest() { - runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_CELLULAR, true /* isSupported */); - runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_WIFI, true /* isSupported */); - runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_BLUETOOTH, true /* isSupported */); - runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_ETHERNET, true /* isSupported */); + runUsageSupportedForUpstreamTypeTest(UT_CELLULAR, true /* isSupported */); + runUsageSupportedForUpstreamTypeTest(UT_WIFI, true /* isSupported */); + runUsageSupportedForUpstreamTypeTest(UT_BLUETOOTH, true /* isSupported */); + runUsageSupportedForUpstreamTypeTest(UT_ETHERNET, true /* isSupported */); runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_WIFI_AWARE, false /* isSupported */); runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_LOWPAN, false /* isSupported */); runUsageSupportedForUpstreamTypeTest(UpstreamType.UT_UNKNOWN, false /* isSupported */); @@ -420,12 +486,138 @@ public final class TetheringMetricsTest { @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2) public void testBuildNetworkTemplateForUpstreamType() { - runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_CELLULAR, MATCH_MOBILE); - runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_WIFI, MATCH_WIFI); - runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_BLUETOOTH, MATCH_BLUETOOTH); - runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_ETHERNET, MATCH_ETHERNET); + runBuildNetworkTemplateForUpstreamType(UT_CELLULAR, MATCH_MOBILE); + runBuildNetworkTemplateForUpstreamType(UT_WIFI, MATCH_WIFI); + runBuildNetworkTemplateForUpstreamType(UT_BLUETOOTH, MATCH_BLUETOOTH); + runBuildNetworkTemplateForUpstreamType(UT_ETHERNET, MATCH_ETHERNET); runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_WIFI_AWARE, MATCH_NONE); runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_LOWPAN, MATCH_NONE); runBuildNetworkTemplateForUpstreamType(UpstreamType.UT_UNKNOWN, MATCH_NONE); } + + private void verifyEmptyUsageForAllUpstreamTypes() { + mHandler.post(() -> { + for (UpstreamType type : UpstreamType.values()) { + assertEquals(EMPTY, mTetheringMetrics.getDataUsageFromUpstreamType(type)); + } + }); + HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT); + } + + @Test + public void testInitializeUpstreamDataUsageBeforeT() { + // Verify the usage is empty for all upstream types before initialization. + verifyEmptyUsageForAllUpstreamTypes(); + + // Verify the usage is still empty after initialization if sdk is lower than T. + doReturn(false).when(mDeps).isUpstreamDataUsageMetricsEnabled(any()); + runAndWaitForIdle(() -> mTetheringMetrics.initUpstreamUsageBaseline()); + verifyEmptyUsageForAllUpstreamTypes(); + } + + private android.app.usage.NetworkStats makeNetworkStatsWithTxRxBytes(DataUsage dataUsage) { + final NetworkStats testAndroidNetStats = + new NetworkStats(0L /* elapsedRealtime */, 1 /* initialSize */).addEntry( + new NetworkStats.Entry("test", 10001, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, dataUsage.rxBytes, + 10, dataUsage.txBytes, 10, 10)); + return makePublicStatsFromAndroidNetStats(testAndroidNetStats); + } + + private static UpstreamType matchRuleToUpstreamType(int matchRule) { + switch (matchRule) { + case MATCH_MOBILE: + return UT_CELLULAR; + case MATCH_WIFI: + return UT_WIFI; + case MATCH_BLUETOOTH: + return UT_BLUETOOTH; + case MATCH_ETHERNET: + return UT_ETHERNET; + default: + return UpstreamType.UT_UNKNOWN; + } + } + + private void initializeUpstreamUsageBaseline() { + doReturn(true).when(mDeps).isUpstreamDataUsageMetricsEnabled(any()); + runAndWaitForIdle(() -> mTetheringMetrics.initUpstreamUsageBaseline()); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testInitUpstreamUsageBaselineAndCleanup() { + // Verify the usage is empty for all upstream types before initialization. + verifyEmptyUsageForAllUpstreamTypes(); + + // Verify the usage has been initialized + initializeUpstreamUsageBaseline(); + + mHandler.post(() -> { + for (UpstreamType type : UpstreamType.values()) { + final DataUsage dataUsage = mTetheringMetrics.getDataUsageFromUpstreamType(type); + if (TetheringMetrics.isUsageSupportedForUpstreamType(type)) { + assertEquals(mMockUpstreamUsageBaseline.get(type), dataUsage); + } else { + assertEquals(EMPTY, dataUsage); + } + } + }); + HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT); + + // Verify the usage is empty after clean up + runAndWaitForIdle(() -> mTetheringMetrics.cleanup()); + verifyEmptyUsageForAllUpstreamTypes(); + } + + private void updateUpstreamDataUsage(UpstreamType type, long usageDiff) { + final DataUsage oldWifiUsage = mMockUpstreamUsageBaseline.get(type); + final DataUsage newWifiUsage = new DataUsage( + oldWifiUsage.txBytes + usageDiff, + oldWifiUsage.rxBytes + usageDiff); + mMockUpstreamUsageBaseline.put(type, newWifiUsage); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testDataUsageCalculation() throws Exception { + initializeUpstreamUsageBaseline(); + runAndWaitForIdle(() -> mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG)); + final long wifiTetheringStartTime = currentTimeMillis(); + incrementCurrentTime(1 * SECOND_IN_MILLIS); + + // Change the upstream to Wi-Fi and update the data usage + runAndWaitForIdle(() -> + mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI))); + final long wifiDuration = 5 * SECOND_IN_MILLIS; + final long wifiUsageDiff = 100L; + incrementCurrentTime(wifiDuration); + updateUpstreamDataUsage(UT_WIFI, wifiUsageDiff); + + // Change the upstream to bluetooth and update the data usage + runAndWaitForIdle(() -> + mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_BLUETOOTH))); + final long bluetoothDuration = 15 * SECOND_IN_MILLIS; + final long btUsageDiff = 50L; + incrementCurrentTime(bluetoothDuration); + updateUpstreamDataUsage(UT_BLUETOOTH, btUsageDiff); + + // Change the upstream to cellular and update the data usage + runAndWaitForIdle(() -> + mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_CELLULAR))); + final long cellDuration = 20 * SECOND_IN_MILLIS; + final long cellUsageDiff = 500L; + incrementCurrentTime(cellDuration); + updateUpstreamDataUsage(UT_CELLULAR, cellUsageDiff); + + // Stop tethering and verify that the data usage is uploaded. + updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR); + UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder(); + addUpstreamEvent(upstreamEvents, UT_WIFI, wifiDuration, wifiUsageDiff, wifiUsageDiff); + addUpstreamEvent(upstreamEvents, UT_BLUETOOTH, bluetoothDuration, btUsageDiff, btUsageDiff); + addUpstreamEvent(upstreamEvents, UT_CELLULAR, cellDuration, cellUsageDiff, cellUsageDiff); + verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, + UserType.USER_SETTINGS, upstreamEvents, + currentTimeMillis() - wifiTetheringStartTime); + } }