1 /*
<lambda>null2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.testutils
18 
19 import android.app.usage.NetworkStatsManager
20 import android.content.Context
21 import android.net.INetworkStatsService
22 import android.net.INetworkStatsSession
23 import android.net.NetworkStats
24 import android.net.NetworkTemplate
25 import android.net.NetworkTemplate.MATCH_MOBILE
26 import android.text.TextUtils
27 import com.android.modules.utils.build.SdkLevel
28 import kotlin.test.assertTrue
29 import org.mockito.ArgumentMatchers.any
30 import org.mockito.ArgumentMatchers.anyBoolean
31 import org.mockito.ArgumentMatchers.anyInt
32 import org.mockito.ArgumentMatchers.anyLong
33 import org.mockito.Mockito
34 
35 @JvmOverloads
36 fun orderInsensitiveEquals(
37     leftStats: NetworkStats,
38     rightStats: NetworkStats,
39     compareTime: Boolean = false
40 ): Boolean {
41     if (leftStats == rightStats) return true
42     if (compareTime && leftStats.elapsedRealtime != rightStats.elapsedRealtime) {
43         return false
44     }
45 
46     // While operations such as add/subtract will preserve empty entries. This will make
47     // the result be hard to verify during test. Remove them before comparing since they
48     // are not really affect correctness.
49     // TODO (b/152827872): Remove empty entries after addition/subtraction.
50     val leftTrimmedEmpty = leftStats.removeEmptyEntries()
51     val rightTrimmedEmpty = rightStats.removeEmptyEntries()
52 
53     if (leftTrimmedEmpty.size() != rightTrimmedEmpty.size()) return false
54     val left = NetworkStats.Entry()
55     val right = NetworkStats.Entry()
56     // Order insensitive compare.
57     for (i in 0 until leftTrimmedEmpty.size()) {
58         leftTrimmedEmpty.getValues(i, left)
59         val j: Int = rightTrimmedEmpty.findIndexHinted(left.iface, left.uid, left.set, left.tag,
60                 left.metered, left.roaming, left.defaultNetwork, i)
61         if (j == -1) return false
62         rightTrimmedEmpty.getValues(j, right)
63         if (SdkLevel.isAtLeastT()) {
64             if (left != right) return false
65         } else {
66             if (!checkEntryEquals(left, right)) return false
67         }
68     }
69     return true
70 }
71 
72 /**
73  * Assert that the two {@link NetworkStats.Entry} are equals.
74  */
assertEntryEqualsnull75 fun assertEntryEquals(left: NetworkStats.Entry, right: NetworkStats.Entry) {
76     assertTrue(checkEntryEquals(left, right))
77 }
78 
79 // TODO: Make all callers use NetworkStats.Entry#equals once S- downstreams
80 //  are no longer supported. Because NetworkStats is mainlined on T+ and
81 //  NetworkStats.Entry#equals in S- does not support null iface.
checkEntryEqualsnull82 fun checkEntryEquals(left: NetworkStats.Entry, right: NetworkStats.Entry): Boolean {
83     return TextUtils.equals(left.iface, right.iface) &&
84             left.uid == right.uid &&
85             left.set == right.set &&
86             left.tag == right.tag &&
87             left.metered == right.metered &&
88             left.roaming == right.roaming &&
89             left.defaultNetwork == right.defaultNetwork &&
90             left.rxBytes == right.rxBytes &&
91             left.rxPackets == right.rxPackets &&
92             left.txBytes == right.txBytes &&
93             left.txPackets == right.txPackets &&
94             left.operations == right.operations
95 }
96 
97 /**
98  * Assert that two {@link NetworkStats} are equals, assuming the order of the records are not
99  * necessarily the same.
100  *
101  * @note {@code elapsedRealtime} is not compared by default, given that in test cases that is not
102  *       usually used.
103  */
104 @JvmOverloads
assertNetworkStatsEqualsnull105 fun assertNetworkStatsEquals(
106     expected: NetworkStats,
107     actual: NetworkStats,
108     compareTime: Boolean = false
109 ) {
110     assertTrue(orderInsensitiveEquals(expected, actual, compareTime),
111             "expected: $expected but was: $actual")
112 }
113 
114 /**
115  * Assert that after being parceled then unparceled, {@link NetworkStats} is equal to the original
116  * object.
117  */
assertParcelingIsLosslessnull118 fun assertParcelingIsLossless(stats: NetworkStats) {
119     assertParcelingIsLossless(stats) { a, b -> orderInsensitiveEquals(a, b) }
120 }
121 
122 /**
123  * Make a {@link android.app.usage.NetworkStats} instance from
124  * a {@link android.net.NetworkStats} instance.
125  */
126 // It's not possible to directly create a mocked `NetworkStats` instance
127 // because of limitations with `NetworkStats#getNextBucket`.
128 // As a workaround for testing, create a mock by controlling the return values
129 // from the mocked service that provides the `NetworkStats` data.
130 // Notes:
131 //   1. The order of records in the final `NetworkStats` object might change or
132 //      some records might be merged if there are items with duplicate keys.
133 //   2. The interface and operations fields will be empty since there is
134 //      no such field in the {@link android.app.usage.NetworkStats}.
makePublicStatsFromAndroidNetStatsnull135 fun makePublicStatsFromAndroidNetStats(androidNetStats: NetworkStats):
136         android.app.usage.NetworkStats {
137     val mockService = Mockito.mock(INetworkStatsService::class.java)
138     val manager = NetworkStatsManager(Mockito.mock(Context::class.java), mockService)
139     val mockStatsSession = Mockito.mock(INetworkStatsSession::class.java)
140 
141     Mockito.doReturn(mockStatsSession).`when`(mockService)
142             .openSessionForUsageStats(anyInt(), any())
143     Mockito.doReturn(androidNetStats).`when`(mockStatsSession).getSummaryForAllUid(
144             any(NetworkTemplate::class.java), anyLong(), anyLong(), anyBoolean())
145     return manager.querySummary(
146             NetworkTemplate.Builder(MATCH_MOBILE).build(),
147             Long.MIN_VALUE, Long.MAX_VALUE
148     )
149 }
150