1 /*
2  * Copyright (C) 2018 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 android.app.usage;
18 
19 import static android.net.ConnectivityManager.TYPE_MOBILE;
20 import static android.net.ConnectivityManager.TYPE_WIFI;
21 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
22 import static android.net.NetworkStats.METERED_NO;
23 import static android.net.NetworkStats.METERED_YES;
24 import static android.net.NetworkStats.ROAMING_NO;
25 import static android.net.NetworkStats.SET_ALL;
26 import static android.net.NetworkStats.SET_DEFAULT;
27 import static android.net.NetworkStats.TAG_NONE;
28 import static android.net.NetworkStatsHistory.FIELD_ALL;
29 import static android.net.NetworkTemplate.MATCH_MOBILE;
30 import static android.net.NetworkTemplate.MATCH_WIFI;
31 
32 import static junit.framework.Assert.assertEquals;
33 import static junit.framework.Assert.assertFalse;
34 import static junit.framework.Assert.assertTrue;
35 
36 import static org.mockito.ArgumentMatchers.any;
37 import static org.mockito.ArgumentMatchers.anyInt;
38 import static org.mockito.ArgumentMatchers.anyLong;
39 import static org.mockito.ArgumentMatchers.anyString;
40 import static org.mockito.ArgumentMatchers.eq;
41 import static org.mockito.Mockito.reset;
42 import static org.mockito.Mockito.times;
43 import static org.mockito.Mockito.verify;
44 import static org.mockito.Mockito.when;
45 
46 import android.net.INetworkStatsService;
47 import android.net.INetworkStatsSession;
48 import android.net.NetworkStats.Entry;
49 import android.net.NetworkStatsHistory;
50 import android.net.NetworkTemplate;
51 import android.os.Build;
52 import android.os.RemoteException;
53 
54 import androidx.test.InstrumentationRegistry;
55 import androidx.test.filters.SmallTest;
56 
57 import com.android.testutils.DevSdkIgnoreRule;
58 import com.android.testutils.DevSdkIgnoreRunner;
59 
60 import org.junit.Before;
61 import org.junit.Test;
62 import org.junit.runner.RunWith;
63 import org.mockito.Mock;
64 import org.mockito.MockitoAnnotations;
65 import org.mockito.invocation.InvocationOnMock;
66 
67 import java.util.Set;
68 
69 @RunWith(DevSdkIgnoreRunner.class)
70 @SmallTest
71 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
72 public class NetworkStatsManagerTest {
73     private static final String TEST_SUBSCRIBER_ID = "subid";
74 
75     private @Mock INetworkStatsService mService;
76     private @Mock INetworkStatsSession mStatsSession;
77 
78     private NetworkStatsManager mManager;
79 
80     // TODO: change to NetworkTemplate.MATCH_MOBILE once internal constant rename is merged to aosp.
81     private static final int MATCH_MOBILE_ALL = 1;
82 
83     @Before
setUp()84     public void setUp() {
85         MockitoAnnotations.initMocks(this);
86         mManager = new NetworkStatsManager(InstrumentationRegistry.getContext(), mService);
87     }
88 
89     @Test
testQueryDetails()90     public void testQueryDetails() throws RemoteException {
91         final long startTime = 1;
92         final long endTime = 100;
93         final int uid1 = 10001;
94         final int uid2 = 10002;
95         final int uid3 = 10003;
96 
97         Entry uid1Entry1 = new Entry("if1", uid1, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
98                 DEFAULT_NETWORK_NO, 100, 10, 200, 20, 0);
99 
100         Entry uid1Entry2 = new Entry("if2", uid1, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
101                 DEFAULT_NETWORK_NO, 100, 10, 200, 20, 0);
102 
103         Entry uid2Entry1 = new Entry("if1", uid2, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
104                 DEFAULT_NETWORK_NO, 150, 10, 250, 20, 0);
105 
106         Entry uid2Entry2 = new Entry("if2", uid2, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
107                 DEFAULT_NETWORK_NO, 150, 10, 250, 20, 0);
108 
109         NetworkStatsHistory history1 = new NetworkStatsHistory(10, 2);
110         history1.recordData(10, 20, uid1Entry1);
111         history1.recordData(20, 30, uid1Entry2);
112 
113         NetworkStatsHistory history2 = new NetworkStatsHistory(10, 2);
114         history1.recordData(30, 40, uid2Entry1);
115         history1.recordData(35, 45, uid2Entry2);
116 
117 
118         when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession);
119         when(mStatsSession.getRelevantUids()).thenReturn(new int[] { uid1, uid2, uid3 });
120 
121         when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class),
122                 eq(uid1), eq(SET_ALL), eq(TAG_NONE),
123                 eq(FIELD_ALL), eq(startTime), eq(endTime)))
124                 .then((InvocationOnMock inv) -> {
125                     NetworkTemplate template = inv.getArgument(0);
126                     assertEquals(MATCH_MOBILE_ALL, template.getMatchRule());
127                     assertEquals(TEST_SUBSCRIBER_ID, template.getSubscriberId());
128                     return history1;
129                 });
130 
131         when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class),
132                 eq(uid2), eq(SET_ALL), eq(TAG_NONE),
133                 eq(FIELD_ALL), eq(startTime), eq(endTime)))
134                 .then((InvocationOnMock inv) -> {
135                     NetworkTemplate template = inv.getArgument(0);
136                     assertEquals(MATCH_MOBILE_ALL, template.getMatchRule());
137                     assertEquals(TEST_SUBSCRIBER_ID, template.getSubscriberId());
138                     return history2;
139                 });
140 
141 
142         NetworkStats stats = mManager.queryDetails(
143                 TYPE_MOBILE, TEST_SUBSCRIBER_ID, startTime, endTime);
144 
145         NetworkStats.Bucket bucket = new NetworkStats.Bucket();
146 
147         // First 2 buckets exactly match entry timings
148         assertTrue(stats.getNextBucket(bucket));
149         assertEquals(10, bucket.getStartTimeStamp());
150         assertEquals(20, bucket.getEndTimeStamp());
151         assertBucketMatches(uid1Entry1, bucket);
152 
153         assertTrue(stats.getNextBucket(bucket));
154         assertEquals(20, bucket.getStartTimeStamp());
155         assertEquals(30, bucket.getEndTimeStamp());
156         assertBucketMatches(uid1Entry2, bucket);
157 
158         // 30 -> 40: contains uid2Entry1 and half of uid2Entry2
159         assertTrue(stats.getNextBucket(bucket));
160         assertEquals(30, bucket.getStartTimeStamp());
161         assertEquals(40, bucket.getEndTimeStamp());
162         assertEquals(225, bucket.getRxBytes());
163         assertEquals(15, bucket.getRxPackets());
164         assertEquals(375, bucket.getTxBytes());
165         assertEquals(30, bucket.getTxPackets());
166 
167         // 40 -> 50: contains half of uid2Entry2
168         assertTrue(stats.getNextBucket(bucket));
169         assertEquals(40, bucket.getStartTimeStamp());
170         assertEquals(50, bucket.getEndTimeStamp());
171         assertEquals(75, bucket.getRxBytes());
172         assertEquals(5, bucket.getRxPackets());
173         assertEquals(125, bucket.getTxBytes());
174         assertEquals(10, bucket.getTxPackets());
175 
176         assertFalse(stats.hasNextBucket());
177     }
178 
runQueryDetailsAndCheckTemplate(int networkType, String subscriberId, NetworkTemplate expectedTemplate)179     private void runQueryDetailsAndCheckTemplate(int networkType, String subscriberId,
180             NetworkTemplate expectedTemplate) throws RemoteException {
181         final long startTime = 1;
182         final long endTime = 100;
183         final int uid1 = 10001;
184         final int uid2 = 10002;
185 
186         reset(mStatsSession);
187         when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession);
188         when(mStatsSession.getRelevantUids()).thenReturn(new int[] { uid1, uid2 });
189         when(mStatsSession.getHistoryIntervalForUid(any(NetworkTemplate.class),
190                 anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyLong()))
191                 .thenReturn(new NetworkStatsHistory(10, 0));
192         NetworkStats stats = mManager.queryDetails(
193                 networkType, subscriberId, startTime, endTime);
194 
195         verify(mStatsSession, times(1)).getHistoryIntervalForUid(
196                 eq(expectedTemplate),
197                 eq(uid1), eq(SET_ALL),
198                 eq(TAG_NONE),
199                 eq(FIELD_ALL), eq(startTime), eq(endTime));
200 
201         verify(mStatsSession, times(1)).getHistoryIntervalForUid(
202                 eq(expectedTemplate),
203                 eq(uid2), eq(SET_ALL),
204                 eq(TAG_NONE),
205                 eq(FIELD_ALL), eq(startTime), eq(endTime));
206 
207         assertFalse(stats.hasNextBucket());
208     }
209 
210     @Test
testNetworkTemplateWhenRunningQueryDetails_NoSubscriberId()211     public void testNetworkTemplateWhenRunningQueryDetails_NoSubscriberId() throws RemoteException {
212         runQueryDetailsAndCheckTemplate(TYPE_MOBILE, null /* subscriberId */,
213                 new NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build());
214         runQueryDetailsAndCheckTemplate(TYPE_WIFI, "" /* subscriberId */,
215                 new NetworkTemplate.Builder(MATCH_WIFI).build());
216         runQueryDetailsAndCheckTemplate(TYPE_WIFI, null /* subscriberId */,
217                 new NetworkTemplate.Builder(MATCH_WIFI).build());
218     }
219 
220     @Test
testNetworkTemplateWhenRunningQueryDetails_MergedCarrierWifi()221     public void testNetworkTemplateWhenRunningQueryDetails_MergedCarrierWifi()
222             throws RemoteException {
223         runQueryDetailsAndCheckTemplate(TYPE_WIFI, TEST_SUBSCRIBER_ID,
224                 new NetworkTemplate.Builder(MATCH_WIFI)
225                         .setSubscriberIds(Set.of(TEST_SUBSCRIBER_ID)).build());
226     }
227 
228     @Test
testQueryTaggedSummary()229     public void testQueryTaggedSummary() throws Exception {
230         final long startTime = 1;
231         final long endTime = 100;
232 
233         reset(mStatsSession);
234         when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession);
235         when(mStatsSession.getTaggedSummaryForAllUid(any(NetworkTemplate.class),
236                 anyLong(), anyLong()))
237                 .thenReturn(new android.net.NetworkStats(0, 0));
238         final NetworkTemplate template = new NetworkTemplate.Builder(MATCH_MOBILE)
239                 .setMeteredness(NetworkStats.Bucket.METERED_YES).build();
240         NetworkStats stats = mManager.queryTaggedSummary(template, startTime, endTime);
241 
242         verify(mStatsSession, times(1)).getTaggedSummaryForAllUid(
243                 eq(template), eq(startTime), eq(endTime));
244 
245         assertFalse(stats.hasNextBucket());
246     }
247 
248 
249     @Test
testQueryDetailsForDevice()250     public void testQueryDetailsForDevice() throws Exception {
251         final long startTime = 1;
252         final long endTime = 100;
253 
254         reset(mStatsSession);
255         when(mService.openSessionForUsageStats(anyInt(), anyString())).thenReturn(mStatsSession);
256         when(mStatsSession.getHistoryIntervalForNetwork(any(NetworkTemplate.class),
257                 anyInt(), anyLong(), anyLong()))
258                 .thenReturn(new NetworkStatsHistory(10, 0));
259         final NetworkTemplate template = new NetworkTemplate.Builder(MATCH_MOBILE)
260                 .setMeteredness(NetworkStats.Bucket.METERED_YES).build();
261         NetworkStats stats = mManager.queryDetailsForDevice(template, startTime, endTime);
262 
263         verify(mStatsSession, times(1)).getHistoryIntervalForNetwork(
264                 eq(template), eq(FIELD_ALL), eq(startTime), eq(endTime));
265 
266         assertFalse(stats.hasNextBucket());
267     }
268 
assertBucketMatches(Entry expected, NetworkStats.Bucket actual)269     private void assertBucketMatches(Entry expected, NetworkStats.Bucket actual) {
270         assertEquals(expected.uid, actual.getUid());
271         assertEquals(expected.rxBytes, actual.getRxBytes());
272         assertEquals(expected.rxPackets, actual.getRxPackets());
273         assertEquals(expected.txBytes, actual.getTxBytes());
274         assertEquals(expected.txPackets, actual.getTxPackets());
275     }
276 }
277