1 /*
2  * 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.internal.telephony.metrics;
18 
19 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
20 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
21 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
22 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
23 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST;
24 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST;
25 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW;
26 
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertNotNull;
29 import static org.junit.Assert.assertNull;
30 import static org.junit.Assert.assertTrue;
31 import static org.mockito.Mockito.anyInt;
32 import static org.mockito.Mockito.anyString;
33 import static org.mockito.Mockito.doReturn;
34 import static org.mockito.Mockito.eq;
35 import static org.mockito.Mockito.inOrder;
36 import static org.mockito.Mockito.times;
37 
38 import android.annotation.Nullable;
39 import android.content.Context;
40 import android.telephony.DisconnectCause;
41 import android.telephony.TelephonyManager;
42 import android.telephony.ims.ImsReasonInfo;
43 import android.test.suitebuilder.annotation.SmallTest;
44 
45 import com.android.internal.telephony.TelephonyTest;
46 import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
47 import com.android.internal.telephony.nano.PersistAtomsProto.RawVoiceCallRatUsage;
48 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
49 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec;
50 import com.android.internal.telephony.protobuf.nano.MessageNano;
51 
52 import org.junit.After;
53 import org.junit.Before;
54 import org.junit.Rule;
55 import org.junit.Test;
56 import org.junit.rules.TemporaryFolder;
57 import org.mockito.ArgumentCaptor;
58 import org.mockito.InOrder;
59 import org.mockito.Mock;
60 
61 import java.io.File;
62 import java.io.FileOutputStream;
63 import java.nio.charset.StandardCharsets;
64 import java.util.Arrays;
65 import java.util.Comparator;
66 
67 public class PersistAtomsStorageTest extends TelephonyTest {
68     private static final String TEST_FILE = "PersistAtomsStorageTest.pb";
69     private static final int MAX_NUM_CALL_SESSIONS = 50;
70     private static final long START_TIME_MILLIS = 2000L;
71     private static final int CARRIER1_ID = 1;
72     private static final int CARRIER2_ID = 1187;
73     private static final int CARRIER3_ID = 1435;
74 
75     @Mock private FileOutputStream mTestFileOutputStream;
76 
77     @Rule public TemporaryFolder mFolder = new TemporaryFolder();
78 
79     private File mTestFile;
80 
81     // call with SRVCC
82     private VoiceCallSession mCall1Proto;
83 
84     // call held after another incoming call, ended before the other call
85     private VoiceCallSession mCall2Proto;
86     private VoiceCallSession mCall3Proto;
87 
88     // failed call
89     private VoiceCallSession mCall4Proto;
90 
91     private RawVoiceCallRatUsage mCarrier1LteUsageProto;
92     private RawVoiceCallRatUsage mCarrier1UmtsUsageProto;
93     private RawVoiceCallRatUsage mCarrier2LteUsageProto;
94     private RawVoiceCallRatUsage mCarrier3LteUsageProto;
95     private RawVoiceCallRatUsage mCarrier3GsmUsageProto;
96 
97     private VoiceCallSession[] mVoiceCallSessions;
98     private RawVoiceCallRatUsage[] mVoiceCallRatUsages;
99 
makeTestData()100     private void makeTestData() {
101         // MO call with SRVCC (LTE to UMTS)
102         mCall1Proto = new VoiceCallSession();
103         mCall1Proto.bearerAtStart = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
104         mCall1Proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
105         mCall1Proto.direction = VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
106         mCall1Proto.setupDuration =
107                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST;
108         mCall1Proto.setupFailed = false;
109         mCall1Proto.disconnectReasonCode = DisconnectCause.LOCAL;
110         mCall1Proto.disconnectExtraCode = 0;
111         mCall1Proto.disconnectExtraMessage = "";
112         mCall1Proto.ratAtStart = TelephonyManager.NETWORK_TYPE_LTE;
113         mCall1Proto.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
114         mCall1Proto.ratSwitchCount = 1L;
115         mCall1Proto.codecBitmask =
116                 (1 << AudioCodec.AUDIO_CODEC_EVS_SWB) | (1 << AudioCodec.AUDIO_CODEC_AMR);
117         mCall1Proto.concurrentCallCountAtStart = 0;
118         mCall1Proto.concurrentCallCountAtEnd = 0;
119         mCall1Proto.simSlotIndex = 0;
120         mCall1Proto.isMultiSim = false;
121         mCall1Proto.isEsim = false;
122         mCall1Proto.carrierId = CARRIER1_ID;
123         mCall1Proto.srvccCompleted = true;
124         mCall1Proto.srvccFailureCount = 0L;
125         mCall1Proto.srvccCancellationCount = 0L;
126         mCall1Proto.rttEnabled = false;
127         mCall1Proto.isEmergency = false;
128         mCall1Proto.isRoaming = false;
129 
130         // VoLTE MT call on DSDS/eSIM, hanged up by remote
131         // concurrent with mCall3Proto, started first and ended first
132         mCall2Proto = new VoiceCallSession();
133         mCall2Proto.bearerAtStart = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
134         mCall2Proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
135         mCall2Proto.direction = VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
136         mCall2Proto.setupDuration =
137                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST;
138         mCall2Proto.setupFailed = false;
139         mCall2Proto.disconnectReasonCode = ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE;
140         mCall2Proto.disconnectExtraCode = 0;
141         mCall2Proto.disconnectExtraMessage = "normal call clearing";
142         mCall2Proto.ratAtStart = TelephonyManager.NETWORK_TYPE_LTE;
143         mCall2Proto.ratAtEnd = TelephonyManager.NETWORK_TYPE_LTE;
144         mCall2Proto.ratSwitchCount = 0L;
145         mCall2Proto.codecBitmask = (1 << AudioCodec.AUDIO_CODEC_EVS_SWB);
146         mCall2Proto.concurrentCallCountAtStart = 0;
147         mCall2Proto.concurrentCallCountAtEnd = 1;
148         mCall2Proto.simSlotIndex = 1;
149         mCall2Proto.isMultiSim = true;
150         mCall2Proto.isEsim = true;
151         mCall2Proto.carrierId = CARRIER2_ID;
152         mCall2Proto.srvccCompleted = false;
153         mCall2Proto.srvccFailureCount = 0L;
154         mCall2Proto.srvccCancellationCount = 0L;
155         mCall2Proto.rttEnabled = false;
156         mCall2Proto.isEmergency = false;
157         mCall2Proto.isRoaming = false;
158 
159         // VoLTE MT call on DSDS/eSIM, hanged up by local, with RTT
160         // concurrent with mCall2Proto, started last and ended last
161         mCall3Proto = new VoiceCallSession();
162         mCall3Proto.bearerAtStart = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
163         mCall3Proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
164         mCall3Proto.direction = VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
165         mCall3Proto.setupDuration =
166                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST;
167         mCall3Proto.setupFailed = false;
168         mCall3Proto.disconnectReasonCode = ImsReasonInfo.CODE_USER_TERMINATED;
169         mCall3Proto.disconnectExtraCode = 0;
170         mCall3Proto.disconnectExtraMessage = "normal call clearing";
171         mCall3Proto.ratAtStart = TelephonyManager.NETWORK_TYPE_LTE;
172         mCall3Proto.ratAtEnd = TelephonyManager.NETWORK_TYPE_LTE;
173         mCall3Proto.ratSwitchCount = 0L;
174         mCall3Proto.codecBitmask = (1 << AudioCodec.AUDIO_CODEC_EVS_SWB);
175         mCall3Proto.concurrentCallCountAtStart = 1;
176         mCall3Proto.concurrentCallCountAtEnd = 0;
177         mCall3Proto.simSlotIndex = 1;
178         mCall3Proto.isMultiSim = true;
179         mCall3Proto.isEsim = true;
180         mCall3Proto.carrierId = CARRIER2_ID;
181         mCall3Proto.srvccCompleted = false;
182         mCall3Proto.srvccFailureCount = 0L;
183         mCall3Proto.srvccCancellationCount = 0L;
184         mCall3Proto.rttEnabled = true;
185         mCall3Proto.isEmergency = false;
186         mCall3Proto.isRoaming = false;
187 
188         // CS MO call while camped on LTE
189         mCall4Proto = new VoiceCallSession();
190         mCall4Proto.bearerAtStart = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
191         mCall4Proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
192         mCall4Proto.direction = VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
193         mCall4Proto.setupDuration =
194                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW;
195         mCall4Proto.setupFailed = true;
196         mCall4Proto.disconnectReasonCode = DisconnectCause.NORMAL;
197         mCall4Proto.disconnectExtraCode = 0;
198         mCall4Proto.disconnectExtraMessage = "";
199         mCall4Proto.ratAtStart = TelephonyManager.NETWORK_TYPE_LTE;
200         mCall4Proto.ratAtEnd = TelephonyManager.NETWORK_TYPE_GSM;
201         mCall4Proto.ratSwitchCount = 1L;
202         mCall4Proto.codecBitmask = (1 << AudioCodec.AUDIO_CODEC_AMR);
203         mCall4Proto.concurrentCallCountAtStart = 0;
204         mCall4Proto.concurrentCallCountAtEnd = 0;
205         mCall4Proto.simSlotIndex = 0;
206         mCall4Proto.isMultiSim = true;
207         mCall4Proto.isEsim = false;
208         mCall4Proto.carrierId = CARRIER3_ID;
209         mCall4Proto.srvccCompleted = false;
210         mCall4Proto.srvccFailureCount = 0L;
211         mCall4Proto.srvccCancellationCount = 0L;
212         mCall4Proto.rttEnabled = false;
213         mCall4Proto.isEmergency = false;
214         mCall4Proto.isRoaming = true;
215 
216         mCarrier1LteUsageProto = new RawVoiceCallRatUsage();
217         mCarrier1LteUsageProto.carrierId = CARRIER1_ID;
218         mCarrier1LteUsageProto.rat = TelephonyManager.NETWORK_TYPE_LTE;
219         mCarrier1LteUsageProto.callCount = 1L;
220         mCarrier1LteUsageProto.totalDurationMillis = 8000L;
221 
222         mCarrier1UmtsUsageProto = new RawVoiceCallRatUsage();
223         mCarrier1UmtsUsageProto.carrierId = CARRIER1_ID;
224         mCarrier1UmtsUsageProto.rat = TelephonyManager.NETWORK_TYPE_UMTS;
225         mCarrier1UmtsUsageProto.callCount = 1L;
226         mCarrier1UmtsUsageProto.totalDurationMillis = 6000L;
227 
228         mCarrier2LteUsageProto = new RawVoiceCallRatUsage();
229         mCarrier2LteUsageProto.carrierId = CARRIER2_ID;
230         mCarrier2LteUsageProto.rat = TelephonyManager.NETWORK_TYPE_LTE;
231         mCarrier2LteUsageProto.callCount = 2L;
232         mCarrier2LteUsageProto.totalDurationMillis = 20000L;
233 
234         mCarrier3LteUsageProto = new RawVoiceCallRatUsage();
235         mCarrier3LteUsageProto.carrierId = CARRIER3_ID;
236         mCarrier3LteUsageProto.rat = TelephonyManager.NETWORK_TYPE_LTE;
237         mCarrier3LteUsageProto.callCount = 1L;
238         mCarrier3LteUsageProto.totalDurationMillis = 1000L;
239 
240         mCarrier3GsmUsageProto = new RawVoiceCallRatUsage();
241         mCarrier3GsmUsageProto.carrierId = CARRIER3_ID;
242         mCarrier3GsmUsageProto.rat = TelephonyManager.NETWORK_TYPE_GSM;
243         mCarrier3GsmUsageProto.callCount = 1L;
244         mCarrier3GsmUsageProto.totalDurationMillis = 100000L;
245 
246         mVoiceCallRatUsages =
247                 new RawVoiceCallRatUsage[] {
248                     mCarrier1UmtsUsageProto,
249                     mCarrier1LteUsageProto,
250                     mCarrier2LteUsageProto,
251                     mCarrier3LteUsageProto,
252                     mCarrier3GsmUsageProto
253                 };
254         mVoiceCallSessions =
255                 new VoiceCallSession[] {mCall1Proto, mCall2Proto, mCall3Proto, mCall4Proto};
256     }
257 
258     private static class TestablePersistAtomsStorage extends PersistAtomsStorage {
259         private long mTimeMillis = START_TIME_MILLIS;
260 
TestablePersistAtomsStorage(Context context)261         TestablePersistAtomsStorage(Context context) {
262             super(context);
263         }
264 
265         @Override
getWallTimeMillis()266         protected long getWallTimeMillis() {
267             // NOTE: super class constructor will be executed before private field is set, which
268             // gives the wrong start time (mTimeMillis will have its default value of 0L)
269             return mTimeMillis == 0L ? START_TIME_MILLIS : mTimeMillis;
270         }
271 
setTimeMillis(long timeMillis)272         private void setTimeMillis(long timeMillis) {
273             mTimeMillis = timeMillis;
274         }
275 
incTimeMillis(long timeMillis)276         private void incTimeMillis(long timeMillis) {
277             mTimeMillis += timeMillis;
278         }
279 
getAtomsProto()280         private PersistAtoms getAtomsProto() {
281             // NOTE: not guarded by mLock as usual, should be fine since the test is single-threaded
282             return mAtoms;
283         }
284     }
285 
286     private TestablePersistAtomsStorage mPersistAtomsStorage;
287 
288     private static final Comparator<MessageNano> sProtoComparator =
289             new Comparator<>() {
290                 @Override
291                 public int compare(MessageNano o1, MessageNano o2) {
292                     if (o1 == o2) {
293                         return 0;
294                     }
295                     if (o1 == null) {
296                         return -1;
297                     }
298                     if (o2 == null) {
299                         return 1;
300                     }
301                     assertEquals(o1.getClass(), o2.getClass());
302                     return o1.toString().compareTo(o2.toString());
303                 }
304             };
305 
306     @Before
setUp()307     public void setUp() throws Exception {
308         super.setUp(getClass().getSimpleName());
309         makeTestData();
310 
311         // by default, test loading with real file IO and saving with mocks
312         mTestFile = mFolder.newFile(TEST_FILE);
313         doReturn(mTestFileOutputStream).when(mContext).openFileOutput(anyString(), anyInt());
314         doReturn(mTestFile).when(mContext).getFileStreamPath(anyString());
315     }
316 
317     @After
tearDown()318     public void tearDown() throws Exception {
319         mTestFile.delete();
320         super.tearDown();
321     }
322 
323     @Test
324     @SmallTest
loadAtoms_fileNotExist()325     public void loadAtoms_fileNotExist() throws Exception {
326         mTestFile.delete();
327 
328         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
329         mPersistAtomsStorage.incTimeMillis(100L);
330 
331         // no exception should be thrown, storage should be empty, pull time should be start time
332         assertEquals(
333                 START_TIME_MILLIS,
334                 mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
335         assertEquals(
336                 START_TIME_MILLIS,
337                 mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
338         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
339         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
340         assertNotNull(voiceCallRatUsage);
341         assertEquals(0, voiceCallRatUsage.length);
342         assertNotNull(voiceCallSession);
343         assertEquals(0, voiceCallSession.length);
344     }
345 
346     @Test
347     @SmallTest
loadAtoms_unreadable()348     public void loadAtoms_unreadable() throws Exception {
349         createEmptyTestFile();
350         mTestFile.setReadable(false);
351 
352         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
353         mPersistAtomsStorage.incTimeMillis(100L);
354 
355         // no exception should be thrown, storage should be empty, pull time should be start time
356         assertEquals(
357                 START_TIME_MILLIS,
358                 mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
359         assertEquals(
360                 START_TIME_MILLIS,
361                 mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
362         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
363         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
364         assertNotNull(voiceCallRatUsage);
365         assertEquals(0, voiceCallRatUsage.length);
366         assertNotNull(voiceCallSession);
367         assertEquals(0, voiceCallSession.length);
368     }
369 
370     @Test
371     @SmallTest
loadAtoms_emptyProto()372     public void loadAtoms_emptyProto() throws Exception {
373         createEmptyTestFile();
374 
375         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
376         mPersistAtomsStorage.incTimeMillis(100L);
377 
378         // no exception should be thrown, storage should be empty, pull time should be start time
379         assertEquals(
380                 START_TIME_MILLIS,
381                 mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
382         assertEquals(
383                 START_TIME_MILLIS,
384                 mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
385         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
386         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
387         assertNotNull(voiceCallRatUsage);
388         assertEquals(0, voiceCallRatUsage.length);
389         assertNotNull(voiceCallSession);
390         assertEquals(0, voiceCallSession.length);
391     }
392 
393     @Test
394     @SmallTest
loadAtoms_malformedFile()395     public void loadAtoms_malformedFile() throws Exception {
396         FileOutputStream stream = new FileOutputStream(mTestFile);
397         stream.write("This is not a proto file.".getBytes(StandardCharsets.UTF_8));
398         stream.close();
399 
400         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
401         mPersistAtomsStorage.incTimeMillis(100L);
402 
403         // no exception should be thrown, storage should be empty, pull time should be start time
404         assertEquals(
405                 START_TIME_MILLIS,
406                 mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
407         assertEquals(
408                 START_TIME_MILLIS,
409                 mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
410         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
411         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
412         assertNotNull(voiceCallRatUsage);
413         assertEquals(0, voiceCallRatUsage.length);
414         assertNotNull(voiceCallSession);
415         assertEquals(0, voiceCallSession.length);
416     }
417 
418     @Test
419     @SmallTest
loadAtoms_pullTimeMissing()420     public void loadAtoms_pullTimeMissing() throws Exception {
421         createTestFile(0L);
422 
423         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
424         mPersistAtomsStorage.incTimeMillis(100L);
425 
426         // no exception should be thrown, storage should be match, pull time should be start time
427         assertEquals(
428                 START_TIME_MILLIS,
429                 mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
430         assertEquals(
431                 START_TIME_MILLIS,
432                 mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
433         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
434         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
435         assertProtoArrayEquals(mVoiceCallRatUsages, voiceCallRatUsage);
436         assertProtoArrayEquals(mVoiceCallSessions, voiceCallSession);
437     }
438 
439     @Test
440     @SmallTest
loadAtoms_validContents()441     public void loadAtoms_validContents() throws Exception {
442         createTestFile(100L);
443 
444         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
445 
446         // no exception should be thrown, storage and pull time should match
447         assertEquals(
448                 100L, mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
449         assertEquals(
450                 100L, mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
451         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
452         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
453         assertProtoArrayEquals(mVoiceCallRatUsages, voiceCallRatUsage);
454         assertProtoArrayEquals(mVoiceCallSessions, voiceCallSession);
455     }
456 
457     @Test
458     @SmallTest
addVoiceCallSession_emptyProto()459     public void addVoiceCallSession_emptyProto() throws Exception {
460         createEmptyTestFile();
461 
462         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
463         mPersistAtomsStorage.addVoiceCallSession(mCall1Proto);
464         mPersistAtomsStorage.incTimeMillis(100L);
465 
466         // call should be added successfully, there should be no RAT usage, changes should be saved
467         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
468         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
469         assertNotNull(voiceCallRatUsage);
470         assertEquals(0, voiceCallRatUsage.length);
471         assertProtoArrayEquals(new VoiceCallSession[] {mCall1Proto}, voiceCallSession);
472         InOrder inOrder = inOrder(mTestFileOutputStream);
473         inOrder.verify(mTestFileOutputStream, times(1))
474                 .write(eq(PersistAtoms.toByteArray(mPersistAtomsStorage.getAtomsProto())));
475         inOrder.verify(mTestFileOutputStream, times(1)).close();
476         inOrder.verifyNoMoreInteractions();
477     }
478 
479     @Test
480     @SmallTest
addVoiceCallSession_withExistingCalls()481     public void addVoiceCallSession_withExistingCalls() throws Exception {
482         createTestFile(100L);
483 
484         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
485         mPersistAtomsStorage.addVoiceCallSession(mCall1Proto);
486         mPersistAtomsStorage.incTimeMillis(100L);
487 
488         // call should be added successfully, RAT usages should not change, changes should be saved
489         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
490         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
491         assertNotNull(voiceCallRatUsage);
492         assertEquals(mVoiceCallRatUsages.length, voiceCallRatUsage.length);
493         assertNotNull(voiceCallSession);
494         // call lists are randomized, but sorted version should be identical
495         VoiceCallSession[] expectedVoiceCallSessions =
496                 new VoiceCallSession[] {
497                     mCall1Proto, mCall1Proto, mCall2Proto, mCall3Proto, mCall4Proto
498                 };
499         Arrays.sort(expectedVoiceCallSessions, sProtoComparator);
500         Arrays.sort(voiceCallSession, sProtoComparator);
501         assertProtoArrayEquals(expectedVoiceCallSessions, voiceCallSession);
502         InOrder inOrder = inOrder(mTestFileOutputStream);
503         inOrder.verify(mTestFileOutputStream, times(1))
504                 .write(eq(PersistAtoms.toByteArray(mPersistAtomsStorage.getAtomsProto())));
505         inOrder.verify(mTestFileOutputStream, times(1)).close();
506         inOrder.verifyNoMoreInteractions();
507     }
508 
509     @Test
510     @SmallTest
addVoiceCallSession_tooManyCalls()511     public void addVoiceCallSession_tooManyCalls() throws Exception {
512         createEmptyTestFile();
513 
514         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
515         addRepeatedCalls(mPersistAtomsStorage, mCall1Proto, 50);
516         mPersistAtomsStorage.addVoiceCallSession(mCall2Proto);
517         mPersistAtomsStorage.incTimeMillis(100L);
518 
519         // one previous call should be evicted, the new call should be added
520         VoiceCallSession[] calls = mPersistAtomsStorage.getVoiceCallSessions(0L);
521         assertHasCall(calls, mCall1Proto, 49);
522         assertHasCall(calls, mCall2Proto, 1);
523     }
524 
525     @Test
526     @SmallTest
addVoiceCallRatUsage_emptyProto()527     public void addVoiceCallRatUsage_emptyProto() throws Exception {
528         createEmptyTestFile();
529         VoiceCallRatTracker ratTracker = VoiceCallRatTracker.fromProto(mVoiceCallRatUsages);
530 
531         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
532         mPersistAtomsStorage.addVoiceCallRatUsage(ratTracker);
533         mPersistAtomsStorage.incTimeMillis(100L);
534 
535         // RAT should be added successfully, calls should not change, changes should be saved
536         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
537         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
538         RawVoiceCallRatUsage[] expectedVoiceCallRatUsage = mVoiceCallRatUsages.clone();
539         Arrays.sort(expectedVoiceCallRatUsage, sProtoComparator);
540         Arrays.sort(voiceCallRatUsage, sProtoComparator);
541         assertProtoArrayEquals(expectedVoiceCallRatUsage, voiceCallRatUsage);
542         assertNotNull(voiceCallSession);
543         assertEquals(0, voiceCallSession.length);
544         InOrder inOrder = inOrder(mTestFileOutputStream);
545         inOrder.verify(mTestFileOutputStream, times(1))
546                 .write(eq(PersistAtoms.toByteArray(mPersistAtomsStorage.getAtomsProto())));
547         inOrder.verify(mTestFileOutputStream, times(1)).close();
548         inOrder.verifyNoMoreInteractions();
549     }
550 
551     @Test
552     @SmallTest
addVoiceCallRatUsage_withExistingUsages()553     public void addVoiceCallRatUsage_withExistingUsages() throws Exception {
554         createTestFile(100L);
555         VoiceCallRatTracker ratTracker = VoiceCallRatTracker.fromProto(mVoiceCallRatUsages);
556 
557         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
558         mPersistAtomsStorage.addVoiceCallRatUsage(ratTracker);
559         mPersistAtomsStorage.incTimeMillis(100L);
560 
561         // RAT should be added successfully, calls should not change, changes should be saved
562         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
563         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
564         // call count and duration should become doubled since mVoiceCallRatUsages applied through
565         // both file and addVoiceCallRatUsage()
566         RawVoiceCallRatUsage[] expectedVoiceCallRatUsage =
567                 multiplyVoiceCallRatUsage(mVoiceCallRatUsages, 2);
568         Arrays.sort(expectedVoiceCallRatUsage, sProtoComparator);
569         Arrays.sort(voiceCallRatUsage, sProtoComparator);
570         assertProtoArrayEquals(expectedVoiceCallRatUsage, voiceCallRatUsage);
571         assertNotNull(voiceCallSession);
572         assertEquals(mVoiceCallSessions.length, voiceCallSession.length);
573         InOrder inOrder = inOrder(mTestFileOutputStream);
574         inOrder.verify(mTestFileOutputStream, times(1))
575                 .write(eq(PersistAtoms.toByteArray(mPersistAtomsStorage.getAtomsProto())));
576         inOrder.verify(mTestFileOutputStream, times(1)).close();
577         inOrder.verifyNoMoreInteractions();
578     }
579 
580     @Test
581     @SmallTest
addVoiceCallRatUsage_empty()582     public void addVoiceCallRatUsage_empty() throws Exception {
583         createEmptyTestFile();
584         VoiceCallRatTracker ratTracker = VoiceCallRatTracker.fromProto(new RawVoiceCallRatUsage[0]);
585 
586         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
587         mPersistAtomsStorage.addVoiceCallRatUsage(ratTracker);
588         mPersistAtomsStorage.incTimeMillis(100L);
589 
590         // RAT should be added successfully, calls should not change
591         // in this case it does not necessarily need to save
592         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(0L);
593         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(0L);
594         assertNotNull(voiceCallRatUsage);
595         assertEquals(0, voiceCallRatUsage.length);
596         assertNotNull(voiceCallSession);
597         assertEquals(0, voiceCallSession.length);
598     }
599 
600     @Test
601     @SmallTest
getVoiceCallRatUsages_tooFrequent()602     public void getVoiceCallRatUsages_tooFrequent() throws Exception {
603         createTestFile(START_TIME_MILLIS);
604 
605         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
606         mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
607         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(100L);
608 
609         // should be denied
610         assertNull(voiceCallRatUsage);
611     }
612 
613     @Test
614     @SmallTest
getVoiceCallRatUsages_withSavedAtoms()615     public void getVoiceCallRatUsages_withSavedAtoms() throws Exception {
616         createTestFile(START_TIME_MILLIS);
617 
618         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
619         mPersistAtomsStorage.incTimeMillis(100L);
620         RawVoiceCallRatUsage[] voiceCallRatUsage1 = mPersistAtomsStorage.getVoiceCallRatUsages(50L);
621         mPersistAtomsStorage.incTimeMillis(100L);
622         RawVoiceCallRatUsage[] voiceCallRatUsage2 = mPersistAtomsStorage.getVoiceCallRatUsages(50L);
623         long voiceCallSessionPullTimestampMillis =
624                 mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis;
625         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(50L);
626 
627         // first set of results should equal to file contents, second should be empty, corresponding
628         // pull timestamp should be updated and saved, other fields should be unaffected
629         assertProtoArrayEquals(mVoiceCallRatUsages, voiceCallRatUsage1);
630         assertProtoArrayEquals(new RawVoiceCallRatUsage[0], voiceCallRatUsage2);
631         assertEquals(
632                 START_TIME_MILLIS + 200L,
633                 mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis);
634         assertProtoArrayEquals(mVoiceCallSessions, voiceCallSession);
635         assertEquals(START_TIME_MILLIS, voiceCallSessionPullTimestampMillis);
636         InOrder inOrder = inOrder(mTestFileOutputStream);
637         assertEquals(
638                 START_TIME_MILLIS + 100L,
639                 getAtomsWritten(inOrder).rawVoiceCallRatUsagePullTimestampMillis);
640         assertEquals(
641                 START_TIME_MILLIS + 200L,
642                 getAtomsWritten(inOrder).rawVoiceCallRatUsagePullTimestampMillis);
643         assertEquals(
644                 START_TIME_MILLIS + 200L,
645                 getAtomsWritten(inOrder).voiceCallSessionPullTimestampMillis);
646         inOrder.verifyNoMoreInteractions();
647     }
648 
649     @Test
650     @SmallTest
getVoiceCallSessions_tooFrequent()651     public void getVoiceCallSessions_tooFrequent() throws Exception {
652         createTestFile(START_TIME_MILLIS);
653 
654         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
655         mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
656         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(100L);
657 
658         // should be denied
659         assertNull(voiceCallSession);
660     }
661 
662     @Test
663     @SmallTest
getVoiceCallSessions_withSavedAtoms()664     public void getVoiceCallSessions_withSavedAtoms() throws Exception {
665         createTestFile(START_TIME_MILLIS);
666 
667         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
668         mPersistAtomsStorage.incTimeMillis(100L);
669         VoiceCallSession[] voiceCallSession1 = mPersistAtomsStorage.getVoiceCallSessions(50L);
670         mPersistAtomsStorage.incTimeMillis(100L);
671         VoiceCallSession[] voiceCallSession2 = mPersistAtomsStorage.getVoiceCallSessions(50L);
672         long voiceCallRatUsagePullTimestampMillis =
673                 mPersistAtomsStorage.getAtomsProto().rawVoiceCallRatUsagePullTimestampMillis;
674         RawVoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(50L);
675 
676         // first set of results should equal to file contents, second should be empty, corresponding
677         // pull timestamp should be updated and saved, other fields should be unaffected
678         assertProtoArrayEquals(mVoiceCallSessions, voiceCallSession1);
679         assertProtoArrayEquals(new VoiceCallSession[0], voiceCallSession2);
680         assertEquals(
681                 START_TIME_MILLIS + 200L,
682                 mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis);
683         assertProtoArrayEquals(mVoiceCallRatUsages, voiceCallRatUsage);
684         assertEquals(START_TIME_MILLIS, voiceCallRatUsagePullTimestampMillis);
685         InOrder inOrder = inOrder(mTestFileOutputStream);
686         assertEquals(
687                 START_TIME_MILLIS + 100L,
688                 getAtomsWritten(inOrder).voiceCallSessionPullTimestampMillis);
689         assertEquals(
690                 START_TIME_MILLIS + 200L,
691                 getAtomsWritten(inOrder).voiceCallSessionPullTimestampMillis);
692         assertEquals(
693                 START_TIME_MILLIS + 200L,
694                 getAtomsWritten(inOrder).rawVoiceCallRatUsagePullTimestampMillis);
695         inOrder.verifyNoMoreInteractions();
696     }
697 
createEmptyTestFile()698     private void createEmptyTestFile() throws Exception {
699         PersistAtoms atoms = new PersistAtoms();
700         FileOutputStream stream = new FileOutputStream(mTestFile);
701         stream.write(PersistAtoms.toByteArray(atoms));
702         stream.close();
703     }
704 
createTestFile(long lastPullTimeMillis)705     private void createTestFile(long lastPullTimeMillis) throws Exception {
706         PersistAtoms atoms = new PersistAtoms();
707         atoms.rawVoiceCallRatUsagePullTimestampMillis = lastPullTimeMillis;
708         atoms.voiceCallSessionPullTimestampMillis = lastPullTimeMillis;
709         atoms.rawVoiceCallRatUsage = mVoiceCallRatUsages;
710         atoms.voiceCallSession = mVoiceCallSessions;
711         FileOutputStream stream = new FileOutputStream(mTestFile);
712         stream.write(PersistAtoms.toByteArray(atoms));
713         stream.close();
714     }
715 
getAtomsWritten(@ullable InOrder inOrder)716     private PersistAtoms getAtomsWritten(@Nullable InOrder inOrder) throws Exception {
717         if (inOrder == null) {
718             inOrder = inOrder(mTestFileOutputStream);
719         }
720         ArgumentCaptor bytesCaptor = ArgumentCaptor.forClass(Object.class);
721         inOrder.verify(mTestFileOutputStream, times(1)).write((byte[]) bytesCaptor.capture());
722         PersistAtoms savedAtoms = PersistAtoms.parseFrom((byte[]) bytesCaptor.getValue());
723         inOrder.verify(mTestFileOutputStream, times(1)).close();
724         return savedAtoms;
725     }
726 
addRepeatedCalls( PersistAtomsStorage storage, VoiceCallSession call, int count)727     private static void addRepeatedCalls(
728             PersistAtomsStorage storage, VoiceCallSession call, int count) {
729         for (int i = 0; i < count; i++) {
730             storage.addVoiceCallSession(call);
731         }
732     }
733 
multiplyVoiceCallRatUsage( RawVoiceCallRatUsage[] usages, int times)734     private static RawVoiceCallRatUsage[] multiplyVoiceCallRatUsage(
735             RawVoiceCallRatUsage[] usages, int times) {
736         RawVoiceCallRatUsage[] multipliedUsages = new RawVoiceCallRatUsage[usages.length];
737         for (int i = 0; i < usages.length; i++) {
738             multipliedUsages[i] = new RawVoiceCallRatUsage();
739             multipliedUsages[i].carrierId = usages[i].carrierId;
740             multipliedUsages[i].rat = usages[i].rat;
741             multipliedUsages[i].callCount = usages[i].callCount * 2;
742             multipliedUsages[i].totalDurationMillis = usages[i].totalDurationMillis * 2;
743         }
744         return multipliedUsages;
745     }
746 
assertProtoArrayEquals(MessageNano[] expected, MessageNano[] actual)747     private static void assertProtoArrayEquals(MessageNano[] expected, MessageNano[] actual) {
748         assertNotNull(expected);
749         assertNotNull(actual);
750         assertEquals(expected.length, actual.length);
751         for (int i = 0; i < expected.length; i++) {
752             assertTrue(
753                     String.format(
754                             "Message %d of %d differs:\n=== expected ===\n%s=== got ===\n%s",
755                             i + 1, expected.length, expected[i].toString(), actual[i].toString()),
756                     MessageNano.messageNanoEquals(expected[i], actual[i]));
757         }
758     }
759 
assertHasCall( VoiceCallSession[] calls, @Nullable VoiceCallSession expectedCall, int expectedCount)760     private static void assertHasCall(
761             VoiceCallSession[] calls, @Nullable VoiceCallSession expectedCall, int expectedCount) {
762         assertNotNull(calls);
763         int actualCount = 0;
764         for (VoiceCallSession call : calls) {
765             if (call != null && expectedCall != null) {
766                 if (MessageNano.messageNanoEquals(call, expectedCall)) {
767                     actualCount++;
768                 }
769             }
770         }
771         assertEquals(expectedCount, actualCount);
772     }
773 }
774