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.CELLULAR_DATA_SERVICE_SWITCH;
20 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE;
21 import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE;
22 import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
23 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
24 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION;
25 
26 import static com.google.common.truth.Truth.assertThat;
27 
28 import static org.mockito.Mockito.anyLong;
29 import static org.mockito.Mockito.doReturn;
30 import static org.mockito.Mockito.eq;
31 import static org.mockito.Mockito.times;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.verifyNoMoreInteractions;
34 
35 import android.app.StatsManager;
36 import android.telephony.TelephonyManager;
37 import android.test.suitebuilder.annotation.SmallTest;
38 import android.util.StatsEvent;
39 
40 import com.android.internal.telephony.Phone;
41 import com.android.internal.telephony.PhoneFactory;
42 import com.android.internal.telephony.TelephonyTest;
43 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
44 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
45 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage;
46 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
47 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
48 import com.android.internal.telephony.uicc.UiccCard;
49 import com.android.internal.telephony.uicc.UiccController;
50 import com.android.internal.telephony.uicc.UiccSlot;
51 
52 import org.junit.After;
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.mockito.Mock;
56 
57 import java.util.ArrayList;
58 import java.util.List;
59 
60 public class MetricsCollectorTest extends TelephonyTest {
61     private static final StatsManager.PullAtomMetadata POLICY_PULL_DAILY =
62             new StatsManager.PullAtomMetadata.Builder()
63                     .setCoolDownMillis(24L * 3600L * 1000L)
64                     .build();
65     private static final long MIN_COOLDOWN_MILLIS = 23L * 3600L * 1000L;
66     private static final long MIN_CALLS_PER_BUCKET = 5L;
67 
68     // NOTE: these fields are currently 32-bit internally and padded to 64-bit by TelephonyManager
69     private static final int SUPPORTED_RAF_1 =
70             (int) TelephonyManager.NETWORK_TYPE_BITMASK_GSM
71                     | (int) TelephonyManager.NETWORK_TYPE_BITMASK_LTE
72                     | (int) TelephonyManager.NETWORK_TYPE_BITMASK_NR;
73     private static final int SUPPORTED_RAF_2 =
74             (int) TelephonyManager.NETWORK_TYPE_BITMASK_GSM
75                     | (int) TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT
76                     | (int) TelephonyManager.NETWORK_TYPE_BITMASK_UMTS;
77     private static final int SUPPORTED_RAF_BOTH = SUPPORTED_RAF_1 | SUPPORTED_RAF_2;
78 
79     // TODO: if we want to check puller registration by mocking StatsManager, we will have to enable
80     // inline mocking since the StatsManager class is final
81 
82     // b/153195691: we cannot verify the contents of StatsEvent as its getters are marked with @hide
83 
84     @Mock private Phone mSecondPhone;
85     @Mock private UiccSlot mPhysicalSlot;
86     @Mock private UiccSlot mEsimSlot;
87     @Mock private UiccCard mActiveCard;
88 
89     @Mock private ServiceStateStats mServiceStateStats;
90 
91     private MetricsCollector mMetricsCollector;
92 
93     @Before
setUp()94     public void setUp() throws Exception {
95         super.setUp(getClass().getSimpleName());
96         mMetricsCollector = new MetricsCollector(mContext);
97         mMetricsCollector.setPersistAtomsStorage(mPersistAtomsStorage);
98         doReturn(mSST).when(mSecondPhone).getServiceStateTracker();
99         doReturn(mServiceStateStats).when(mSST).getServiceStateStats();
100     }
101 
102     @After
tearDown()103     public void tearDown() throws Exception {
104         super.tearDown();
105     }
106 
107     @Test
108     @SmallTest
onPullAtom_simSlotState_bothSimPresent()109     public void onPullAtom_simSlotState_bothSimPresent() {
110         // these have been tested extensively in SimSlotStateTest, here we verify atom generation
111         doReturn(true).when(mPhysicalSlot).isActive();
112         doReturn(CardState.CARDSTATE_PRESENT).when(mPhysicalSlot).getCardState();
113         doReturn(false).when(mPhysicalSlot).isEuicc();
114         doReturn(true).when(mEsimSlot).isActive();
115         doReturn(CardState.CARDSTATE_PRESENT).when(mEsimSlot).getCardState();
116         doReturn(true).when(mEsimSlot).isEuicc();
117         doReturn(mActiveCard).when(mEsimSlot).getUiccCard();
118         doReturn(4).when(mActiveCard).getNumApplications();
119         doReturn(new UiccSlot[] {mPhysicalSlot, mEsimSlot}).when(mUiccController).getUiccSlots();
120         doReturn(mPhysicalSlot).when(mUiccController).getUiccSlot(eq(0));
121         doReturn(mEsimSlot).when(mUiccController).getUiccSlot(eq(1));
122         StatsEvent expectedAtom =
123                 StatsEvent.newBuilder()
124                         .setAtomId(SIM_SLOT_STATE)
125                         .writeInt(2)
126                         .writeInt(2)
127                         .writeInt(1)
128                         .build();
129         List<StatsEvent> actualAtoms = new ArrayList<>();
130 
131         int result = mMetricsCollector.onPullAtom(SIM_SLOT_STATE, actualAtoms);
132 
133         assertThat(actualAtoms).hasSize(1);
134         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
135         // TODO(b/153196254): verify atom contents
136     }
137 
138     @Test
139     @SmallTest
onPullAtom_simSlotState_beforeUiccControllerReady()140     public void onPullAtom_simSlotState_beforeUiccControllerReady() throws Exception {
141         // there is a slight chance that MetricsCollector gets pulled after registration while
142         // PhoneFactory havne't made UiccController yet, RuntimeException will be thrown
143         replaceInstance(UiccController.class, "mInstance", mUiccController, null);
144         List<StatsEvent> actualAtoms = new ArrayList<>();
145 
146         int result = mMetricsCollector.onPullAtom(SIM_SLOT_STATE, actualAtoms);
147 
148         assertThat(actualAtoms).hasSize(0);
149         assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
150     }
151 
152     @Test
153     @SmallTest
onPullAtom_supportedRadioAccessFamily_singlePhone()154     public void onPullAtom_supportedRadioAccessFamily_singlePhone() {
155         doReturn(SUPPORTED_RAF_1).when(mPhone).getRadioAccessFamily();
156         StatsEvent expectedAtom =
157                 StatsEvent.newBuilder()
158                         .setAtomId(SUPPORTED_RADIO_ACCESS_FAMILY)
159                         .writeLong(SUPPORTED_RAF_1)
160                         .build();
161         List<StatsEvent> actualAtoms = new ArrayList<>();
162 
163         int result = mMetricsCollector.onPullAtom(SUPPORTED_RADIO_ACCESS_FAMILY, actualAtoms);
164 
165         assertThat(actualAtoms).hasSize(1);
166         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
167         // TODO(b/153196254): verify atom contents
168     }
169 
170     @Test
171     @SmallTest
onPullAtom_supportedRadioAccessFamily_dualPhones()172     public void onPullAtom_supportedRadioAccessFamily_dualPhones() {
173         doReturn(SUPPORTED_RAF_1).when(mPhone).getRadioAccessFamily();
174         doReturn(SUPPORTED_RAF_2).when(mSecondPhone).getRadioAccessFamily();
175         mPhones = new Phone[] {mPhone, mSecondPhone};
176         StatsEvent expectedAtom =
177                 StatsEvent.newBuilder()
178                         .setAtomId(SUPPORTED_RADIO_ACCESS_FAMILY)
179                         .writeLong(SUPPORTED_RAF_BOTH)
180                         .build();
181         List<StatsEvent> actualAtoms = new ArrayList<>();
182 
183         int result = mMetricsCollector.onPullAtom(SUPPORTED_RADIO_ACCESS_FAMILY, actualAtoms);
184 
185         assertThat(actualAtoms).hasSize(1);
186         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
187         // TODO(b/153196254): verify atom contents
188     }
189 
190     @Test
191     @SmallTest
onPullAtom_supportedRadioAccessFamily_dualPhonesWithUnknownRaf()192     public void onPullAtom_supportedRadioAccessFamily_dualPhonesWithUnknownRaf() {
193         doReturn(SUPPORTED_RAF_1).when(mPhone).getRadioAccessFamily();
194         doReturn((int) TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN)
195                 .when(mSecondPhone)
196                 .getRadioAccessFamily();
197         mPhones = new Phone[] {mPhone, mSecondPhone};
198         StatsEvent expectedAtom =
199                 StatsEvent.newBuilder()
200                         .setAtomId(SUPPORTED_RADIO_ACCESS_FAMILY)
201                         .writeLong(SUPPORTED_RAF_1)
202                         .build();
203         List<StatsEvent> actualAtoms = new ArrayList<>();
204 
205         int result = mMetricsCollector.onPullAtom(SUPPORTED_RADIO_ACCESS_FAMILY, actualAtoms);
206 
207         assertThat(actualAtoms).hasSize(1);
208         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
209         // TODO(b/153196254): verify atom contents
210     }
211 
212     @Test
213     @SmallTest
onPullAtom_supportedRadioAccessFamily_beforePhoneReady()214     public void onPullAtom_supportedRadioAccessFamily_beforePhoneReady() throws Exception {
215         replaceInstance(PhoneFactory.class, "sMadeDefaults", true, false);
216         List<StatsEvent> actualAtoms = new ArrayList<>();
217 
218         int result = mMetricsCollector.onPullAtom(SUPPORTED_RADIO_ACCESS_FAMILY, actualAtoms);
219 
220         assertThat(actualAtoms).hasSize(0);
221         assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
222     }
223 
224     @Test
225     @SmallTest
onPullAtom_voiceCallRatUsage_empty()226     public void onPullAtom_voiceCallRatUsage_empty() throws Exception {
227         doReturn(new VoiceCallRatUsage[0])
228                 .when(mPersistAtomsStorage)
229                 .getVoiceCallRatUsages(anyLong());
230         List<StatsEvent> actualAtoms = new ArrayList<>();
231 
232         int result = mMetricsCollector.onPullAtom(VOICE_CALL_RAT_USAGE, actualAtoms);
233 
234         assertThat(actualAtoms).hasSize(0);
235         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
236     }
237 
238     @Test
239     @SmallTest
onPullAtom_voiceCallRatUsage_tooFrequent()240     public void onPullAtom_voiceCallRatUsage_tooFrequent() throws Exception {
241         doReturn(null).when(mPersistAtomsStorage).getVoiceCallRatUsages(anyLong());
242         List<StatsEvent> actualAtoms = new ArrayList<>();
243 
244         int result = mMetricsCollector.onPullAtom(VOICE_CALL_RAT_USAGE, actualAtoms);
245 
246         assertThat(actualAtoms).hasSize(0);
247         assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
248         verify(mPersistAtomsStorage, times(1)).getVoiceCallRatUsages(eq(MIN_COOLDOWN_MILLIS));
249         verifyNoMoreInteractions(mPersistAtomsStorage);
250     }
251 
252     @Test
253     @SmallTest
onPullAtom_voiceCallRatUsage_bucketWithTooFewCalls()254     public void onPullAtom_voiceCallRatUsage_bucketWithTooFewCalls() throws Exception {
255         VoiceCallRatUsage usage1 = new VoiceCallRatUsage();
256         usage1.callCount = MIN_CALLS_PER_BUCKET;
257         VoiceCallRatUsage usage2 = new VoiceCallRatUsage();
258         usage2.callCount = MIN_CALLS_PER_BUCKET - 1L;
259         doReturn(new VoiceCallRatUsage[] {usage1, usage1, usage1, usage2})
260                 .when(mPersistAtomsStorage)
261                 .getVoiceCallRatUsages(anyLong());
262         List<StatsEvent> actualAtoms = new ArrayList<>();
263 
264         int result = mMetricsCollector.onPullAtom(VOICE_CALL_RAT_USAGE, actualAtoms);
265 
266         assertThat(actualAtoms).hasSize(3); // usage 2 should be dropped
267         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
268         // TODO(b/153196254): verify atom contents
269     }
270 
271     @Test
272     @SmallTest
onPullAtom_voiceCallSession_empty()273     public void onPullAtom_voiceCallSession_empty() throws Exception {
274         doReturn(new VoiceCallSession[0])
275                 .when(mPersistAtomsStorage)
276                 .getVoiceCallSessions(anyLong());
277         List<StatsEvent> actualAtoms = new ArrayList<>();
278 
279         int result = mMetricsCollector.onPullAtom(VOICE_CALL_SESSION, actualAtoms);
280 
281         assertThat(actualAtoms).hasSize(0);
282         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
283     }
284 
285     @Test
286     @SmallTest
onPullAtom_voiceCallSession_tooFrequent()287     public void onPullAtom_voiceCallSession_tooFrequent() throws Exception {
288         doReturn(null).when(mPersistAtomsStorage).getVoiceCallSessions(anyLong());
289         List<StatsEvent> actualAtoms = new ArrayList<>();
290 
291         int result = mMetricsCollector.onPullAtom(VOICE_CALL_SESSION, actualAtoms);
292 
293         assertThat(actualAtoms).hasSize(0);
294         assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
295         verify(mPersistAtomsStorage, times(1)).getVoiceCallSessions(eq(MIN_COOLDOWN_MILLIS));
296         verifyNoMoreInteractions(mPersistAtomsStorage);
297     }
298 
299     @Test
300     @SmallTest
onPullAtom_voiceCallSession_multipleCalls()301     public void onPullAtom_voiceCallSession_multipleCalls() throws Exception {
302         VoiceCallSession call = new VoiceCallSession();
303         doReturn(new VoiceCallSession[] {call, call, call, call})
304                 .when(mPersistAtomsStorage)
305                 .getVoiceCallSessions(anyLong());
306         List<StatsEvent> actualAtoms = new ArrayList<>();
307 
308         int result = mMetricsCollector.onPullAtom(VOICE_CALL_SESSION, actualAtoms);
309 
310         assertThat(actualAtoms).hasSize(4);
311         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
312         // TODO(b/153196254): verify atom contents
313     }
314 
315     @Test
316     @SmallTest
onPullAtom_cellularDataServiceSwitch_empty()317     public void onPullAtom_cellularDataServiceSwitch_empty() throws Exception {
318         doReturn(new CellularDataServiceSwitch[0])
319                 .when(mPersistAtomsStorage)
320                 .getCellularDataServiceSwitches(anyLong());
321         List<StatsEvent> actualAtoms = new ArrayList<>();
322 
323         int result = mMetricsCollector.onPullAtom(CELLULAR_DATA_SERVICE_SWITCH, actualAtoms);
324 
325         assertThat(actualAtoms).hasSize(0);
326         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
327         // TODO(b/153196254): verify atom contents
328     }
329 
330     @Test
331     @SmallTest
onPullAtom_cellularDataServiceSwitch_tooFrequent()332     public void onPullAtom_cellularDataServiceSwitch_tooFrequent() throws Exception {
333         doReturn(null).when(mPersistAtomsStorage).getCellularDataServiceSwitches(anyLong());
334         List<StatsEvent> actualAtoms = new ArrayList<>();
335 
336         int result = mMetricsCollector.onPullAtom(CELLULAR_DATA_SERVICE_SWITCH, actualAtoms);
337 
338         assertThat(actualAtoms).hasSize(0);
339         assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
340         verify(mPersistAtomsStorage, times(1))
341                 .getCellularDataServiceSwitches(eq(MIN_COOLDOWN_MILLIS));
342         verifyNoMoreInteractions(mPersistAtomsStorage);
343     }
344 
345     @Test
346     @SmallTest
onPullAtom_cellularDataServiceSwitch_multipleSwitches()347     public void onPullAtom_cellularDataServiceSwitch_multipleSwitches() throws Exception {
348         CellularDataServiceSwitch serviceSwitch = new CellularDataServiceSwitch();
349         doReturn(new CellularDataServiceSwitch[] {serviceSwitch, serviceSwitch, serviceSwitch})
350                 .when(mPersistAtomsStorage)
351                 .getCellularDataServiceSwitches(anyLong());
352         List<StatsEvent> actualAtoms = new ArrayList<>();
353 
354         int result = mMetricsCollector.onPullAtom(CELLULAR_DATA_SERVICE_SWITCH, actualAtoms);
355 
356         assertThat(actualAtoms).hasSize(3);
357         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
358         // TODO(b/153196254): verify atom contents
359     }
360 
361     @Test
362     @SmallTest
onPullAtom_cellularServiceState_empty()363     public void onPullAtom_cellularServiceState_empty() throws Exception {
364         doReturn(new CellularServiceState[0])
365                 .when(mPersistAtomsStorage)
366                 .getCellularServiceStates(anyLong());
367         List<StatsEvent> actualAtoms = new ArrayList<>();
368 
369         int result = mMetricsCollector.onPullAtom(CELLULAR_SERVICE_STATE, actualAtoms);
370 
371         assertThat(actualAtoms).hasSize(0);
372         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
373         // TODO(b/153196254): verify atom contents
374     }
375 
376     @Test
377     @SmallTest
onPullAtom_cellularServiceState_tooFrequent()378     public void onPullAtom_cellularServiceState_tooFrequent() throws Exception {
379         doReturn(null).when(mPersistAtomsStorage).getCellularServiceStates(anyLong());
380         List<StatsEvent> actualAtoms = new ArrayList<>();
381 
382         int result = mMetricsCollector.onPullAtom(CELLULAR_SERVICE_STATE, actualAtoms);
383 
384         assertThat(actualAtoms).hasSize(0);
385         assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
386         verify(mPersistAtomsStorage, times(1)).getCellularServiceStates(eq(MIN_COOLDOWN_MILLIS));
387         verifyNoMoreInteractions(mPersistAtomsStorage);
388     }
389 
390     @Test
391     @SmallTest
onPullAtom_cellularServiceState_multipleStates()392     public void onPullAtom_cellularServiceState_multipleStates() throws Exception {
393         CellularServiceState state = new CellularServiceState();
394         doReturn(new CellularServiceState[] {state, state, state})
395                 .when(mPersistAtomsStorage)
396                 .getCellularServiceStates(anyLong());
397         List<StatsEvent> actualAtoms = new ArrayList<>();
398 
399         int result = mMetricsCollector.onPullAtom(CELLULAR_SERVICE_STATE, actualAtoms);
400 
401         assertThat(actualAtoms).hasSize(3);
402         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
403         // TODO(b/153196254): verify atom contents
404     }
405 }
406