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