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 android.text.format.DateUtils.DAY_IN_MILLIS; 20 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.content.pm.PackageManager; 24 import android.os.Build; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.telephony.TelephonyManager; 28 import android.telephony.TelephonyManager.NetworkTypeBitMask; 29 import android.util.SparseIntArray; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierIdMismatch; 33 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; 34 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState; 35 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; 36 import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent; 37 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent; 38 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent; 39 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationFeatureTagStats; 40 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationServiceDescStats; 41 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats; 42 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination; 43 import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms; 44 import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequestsV2; 45 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms; 46 import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms; 47 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent; 48 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats; 49 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats; 50 import com.android.internal.telephony.nano.PersistAtomsProto.SipDelegateStats; 51 import com.android.internal.telephony.nano.PersistAtomsProto.SipMessageResponse; 52 import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportFeatureTagStats; 53 import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportSession; 54 import com.android.internal.telephony.nano.PersistAtomsProto.UceEventStats; 55 import com.android.internal.telephony.nano.PersistAtomsProto.UnmeteredNetworks; 56 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage; 57 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession; 58 import com.android.internal.util.ArrayUtils; 59 import com.android.telephony.Rlog; 60 61 import java.io.FileOutputStream; 62 import java.io.IOException; 63 import java.nio.file.Files; 64 import java.nio.file.NoSuchFileException; 65 import java.security.SecureRandom; 66 import java.util.Arrays; 67 import java.util.Comparator; 68 import java.util.stream.IntStream; 69 70 /** 71 * Stores and aggregates metrics that should not be pulled at arbitrary frequency. 72 * 73 * <p>NOTE: while this class checks timestamp against {@code minIntervalMillis}, it is {@link 74 * MetricsCollector}'s responsibility to ensure {@code minIntervalMillis} is set correctly. 75 */ 76 public class PersistAtomsStorage { 77 private static final String TAG = PersistAtomsStorage.class.getSimpleName(); 78 79 /** Name of the file where cached statistics are saved to. */ 80 private static final String FILENAME = "persist_atoms.pb"; 81 82 /** Delay to store atoms to persistent storage to bundle multiple operations together. */ 83 private static final int SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS = 30000; 84 85 /** 86 * Delay to store atoms to persistent storage during pulls to avoid unnecessary operations. 87 * 88 * <p>This delay should be short to avoid duplicating atoms or losing pull timestamp in case of 89 * crash or power loss. 90 */ 91 private static final int SAVE_TO_FILE_DELAY_FOR_GET_MILLIS = 500; 92 93 /** Maximum number of call sessions to store between pulls. */ 94 private final int mMaxNumVoiceCallSessions; 95 96 /** 97 * Maximum number of SMS to store between pulls. Incoming messages and outgoing messages are 98 * counted separately. 99 */ 100 private final int mMaxNumSms; 101 102 /** 103 * Maximum number of carrier ID mismatch events stored on the device to avoid sending duplicated 104 * metrics. 105 */ 106 private final int mMaxNumCarrierIdMismatches; 107 108 /** Maximum number of data call sessions to store during pulls. */ 109 private final int mMaxNumDataCallSessions; 110 111 /** Maximum number of service states to store between pulls. */ 112 private final int mMaxNumCellularServiceStates; 113 114 /** Maximum number of data service switches to store between pulls. */ 115 private final int mMaxNumCellularDataSwitches; 116 117 /** Maximum number of IMS registration stats to store between pulls. */ 118 private final int mMaxNumImsRegistrationStats; 119 120 /** Maximum number of IMS registration terminations to store between pulls. */ 121 private final int mMaxNumImsRegistrationTerminations; 122 123 /** Maximum number of IMS Registration Feature Tags to store between pulls. */ 124 private final int mMaxNumImsRegistrationFeatureStats; 125 126 /** Maximum number of RCS Client Provisioning to store between pulls. */ 127 private final int mMaxNumRcsClientProvisioningStats; 128 129 /** Maximum number of RCS Acs Provisioning to store between pulls. */ 130 private final int mMaxNumRcsAcsProvisioningStats; 131 132 /** Maximum number of Sip Message Response to store between pulls. */ 133 private final int mMaxNumSipMessageResponseStats; 134 135 /** Maximum number of Sip Transport Session to store between pulls. */ 136 private final int mMaxNumSipTransportSessionStats; 137 138 /** Maximum number of Sip Delegate to store between pulls. */ 139 private final int mMaxNumSipDelegateStats; 140 141 /** Maximum number of Sip Transport Feature Tag to store between pulls. */ 142 private final int mMaxNumSipTransportFeatureTagStats; 143 144 /** Maximum number of Dedicated Bearer Listener Event to store between pulls. */ 145 private final int mMaxNumDedicatedBearerListenerEventStats; 146 147 /** Maximum number of Dedicated Bearer Event to store between pulls. */ 148 private final int mMaxNumDedicatedBearerEventStats; 149 150 /** Maximum number of IMS Registration Service Desc to store between pulls. */ 151 private final int mMaxNumImsRegistrationServiceDescStats; 152 153 /** Maximum number of UCE Event to store between pulls. */ 154 private final int mMaxNumUceEventStats; 155 156 /** Maximum number of Presence Notify Event to store between pulls. */ 157 private final int mMaxNumPresenceNotifyEventStats; 158 159 /** Maximum number of GBA Event to store between pulls. */ 160 private final int mMaxNumGbaEventStats; 161 162 /** Stores persist atoms and persist states of the puller. */ 163 @VisibleForTesting protected PersistAtoms mAtoms; 164 165 /** Aggregates RAT duration and call count. */ 166 private final VoiceCallRatTracker mVoiceCallRatTracker; 167 168 /** Whether atoms should be saved immediately, skipping the delay. */ 169 @VisibleForTesting protected boolean mSaveImmediately; 170 171 private final Context mContext; 172 private final Handler mHandler; 173 private final HandlerThread mHandlerThread; 174 private static final SecureRandom sRandom = new SecureRandom(); 175 176 private Runnable mSaveRunnable = 177 new Runnable() { 178 @Override 179 public void run() { 180 saveAtomsToFileNow(); 181 } 182 }; 183 PersistAtomsStorage(Context context)184 public PersistAtomsStorage(Context context) { 185 mContext = context; 186 187 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_RAM_LOW)) { 188 Rlog.i(TAG, "Low RAM device"); 189 mMaxNumVoiceCallSessions = 10; 190 mMaxNumSms = 5; 191 mMaxNumCarrierIdMismatches = 8; 192 mMaxNumDataCallSessions = 5; 193 mMaxNumCellularServiceStates = 10; 194 mMaxNumCellularDataSwitches = 5; 195 mMaxNumImsRegistrationStats = 5; 196 mMaxNumImsRegistrationTerminations = 5; 197 mMaxNumImsRegistrationFeatureStats = 15; 198 mMaxNumRcsClientProvisioningStats = 5; 199 mMaxNumRcsAcsProvisioningStats = 5; 200 mMaxNumSipMessageResponseStats = 10; 201 mMaxNumSipTransportSessionStats = 10; 202 mMaxNumSipDelegateStats = 5; 203 mMaxNumSipTransportFeatureTagStats = 15; 204 mMaxNumDedicatedBearerListenerEventStats = 5; 205 mMaxNumDedicatedBearerEventStats = 5; 206 mMaxNumImsRegistrationServiceDescStats = 15; 207 mMaxNumUceEventStats = 5; 208 mMaxNumPresenceNotifyEventStats = 10; 209 mMaxNumGbaEventStats = 5; 210 } else { 211 mMaxNumVoiceCallSessions = 50; 212 mMaxNumSms = 25; 213 mMaxNumCarrierIdMismatches = 40; 214 mMaxNumDataCallSessions = 15; 215 mMaxNumCellularServiceStates = 50; 216 mMaxNumCellularDataSwitches = 50; 217 mMaxNumImsRegistrationStats = 10; 218 mMaxNumImsRegistrationTerminations = 10; 219 mMaxNumImsRegistrationFeatureStats = 25; 220 mMaxNumRcsClientProvisioningStats = 10; 221 mMaxNumRcsAcsProvisioningStats = 10; 222 mMaxNumSipMessageResponseStats = 25; 223 mMaxNumSipTransportSessionStats = 25; 224 mMaxNumSipDelegateStats = 10; 225 mMaxNumSipTransportFeatureTagStats = 25; 226 mMaxNumDedicatedBearerListenerEventStats = 10; 227 mMaxNumDedicatedBearerEventStats = 10; 228 mMaxNumImsRegistrationServiceDescStats = 25; 229 mMaxNumUceEventStats = 25; 230 mMaxNumPresenceNotifyEventStats = 50; 231 mMaxNumGbaEventStats = 10; 232 } 233 234 mAtoms = loadAtomsFromFile(); 235 mVoiceCallRatTracker = VoiceCallRatTracker.fromProto(mAtoms.voiceCallRatUsage); 236 237 mHandlerThread = new HandlerThread("PersistAtomsThread"); 238 mHandlerThread.start(); 239 mHandler = new Handler(mHandlerThread.getLooper()); 240 mSaveImmediately = false; 241 } 242 243 /** Adds a call to the storage. */ addVoiceCallSession(VoiceCallSession call)244 public synchronized void addVoiceCallSession(VoiceCallSession call) { 245 mAtoms.voiceCallSession = 246 insertAtRandomPlace(mAtoms.voiceCallSession, call, mMaxNumVoiceCallSessions); 247 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 248 249 Rlog.d(TAG, "Add new voice call session: " + call.toString()); 250 } 251 252 /** Adds RAT usages to the storage when a call session ends. */ addVoiceCallRatUsage(VoiceCallRatTracker ratUsages)253 public synchronized void addVoiceCallRatUsage(VoiceCallRatTracker ratUsages) { 254 mVoiceCallRatTracker.mergeWith(ratUsages); 255 mAtoms.voiceCallRatUsage = mVoiceCallRatTracker.toProto(); 256 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 257 } 258 259 /** Adds an incoming SMS to the storage. */ addIncomingSms(IncomingSms sms)260 public synchronized void addIncomingSms(IncomingSms sms) { 261 sms.hashCode = SmsStats.getSmsHashCode(sms); 262 mAtoms.incomingSms = insertAtRandomPlace(mAtoms.incomingSms, sms, mMaxNumSms); 263 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 264 265 // To be removed 266 Rlog.d(TAG, "Add new incoming SMS atom: " + sms.toString()); 267 } 268 269 /** Adds an outgoing SMS to the storage. */ addOutgoingSms(OutgoingSms sms)270 public synchronized void addOutgoingSms(OutgoingSms sms) { 271 sms.hashCode = SmsStats.getSmsHashCode(sms); 272 // Update the retry id, if needed, so that it's unique and larger than all 273 // previous ones. (this algorithm ignores the fact that some SMS atoms might 274 // be dropped due to limit in size of the array). 275 for (OutgoingSms storedSms : mAtoms.outgoingSms) { 276 if (storedSms.messageId == sms.messageId && storedSms.retryId >= sms.retryId) { 277 sms.retryId = storedSms.retryId + 1; 278 } 279 } 280 281 mAtoms.outgoingSms = insertAtRandomPlace(mAtoms.outgoingSms, sms, mMaxNumSms); 282 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 283 284 // To be removed 285 Rlog.d(TAG, "Add new outgoing SMS atom: " + sms.toString()); 286 } 287 288 /** Adds a service state to the storage, together with data service switch if any. */ addCellularServiceStateAndCellularDataServiceSwitch( CellularServiceState state, @Nullable CellularDataServiceSwitch serviceSwitch)289 public synchronized void addCellularServiceStateAndCellularDataServiceSwitch( 290 CellularServiceState state, @Nullable CellularDataServiceSwitch serviceSwitch) { 291 CellularServiceState existingState = find(state); 292 if (existingState != null) { 293 existingState.totalTimeMillis += state.totalTimeMillis; 294 existingState.lastUsedMillis = getWallTimeMillis(); 295 } else { 296 state.lastUsedMillis = getWallTimeMillis(); 297 mAtoms.cellularServiceState = 298 insertAtRandomPlace( 299 mAtoms.cellularServiceState, state, mMaxNumCellularServiceStates); 300 } 301 302 if (serviceSwitch != null) { 303 CellularDataServiceSwitch existingSwitch = find(serviceSwitch); 304 if (existingSwitch != null) { 305 existingSwitch.switchCount += serviceSwitch.switchCount; 306 existingSwitch.lastUsedMillis = getWallTimeMillis(); 307 } else { 308 serviceSwitch.lastUsedMillis = getWallTimeMillis(); 309 mAtoms.cellularDataServiceSwitch = 310 insertAtRandomPlace( 311 mAtoms.cellularDataServiceSwitch, 312 serviceSwitch, 313 mMaxNumCellularDataSwitches); 314 } 315 } 316 317 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 318 } 319 320 /** Adds a data call session to the storage. */ addDataCallSession(DataCallSession dataCall)321 public synchronized void addDataCallSession(DataCallSession dataCall) { 322 int index = findIndex(dataCall); 323 if (index >= 0) { 324 DataCallSession existingCall = mAtoms.dataCallSession[index]; 325 dataCall.ratSwitchCount += existingCall.ratSwitchCount; 326 dataCall.durationMinutes += existingCall.durationMinutes; 327 328 dataCall.handoverFailureCauses = IntStream.concat(Arrays.stream( 329 dataCall.handoverFailureCauses), 330 Arrays.stream(existingCall.handoverFailureCauses)) 331 .limit(DataCallSessionStats.SIZE_LIMIT_HANDOVER_FAILURES).toArray(); 332 dataCall.handoverFailureRat = IntStream.concat(Arrays.stream( 333 dataCall.handoverFailureRat), 334 Arrays.stream(existingCall.handoverFailureRat)) 335 .limit(DataCallSessionStats.SIZE_LIMIT_HANDOVER_FAILURES).toArray(); 336 337 mAtoms.dataCallSession[index] = dataCall; 338 } else { 339 mAtoms.dataCallSession = 340 insertAtRandomPlace(mAtoms.dataCallSession, dataCall, mMaxNumDataCallSessions); 341 } 342 343 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 344 } 345 346 /** 347 * Adds a new carrier ID mismatch event to the storage. 348 * 349 * @return true if the item was not present and was added to the persistent storage, false 350 * otherwise. 351 */ addCarrierIdMismatch(CarrierIdMismatch carrierIdMismatch)352 public synchronized boolean addCarrierIdMismatch(CarrierIdMismatch carrierIdMismatch) { 353 // Check if the details of the SIM cards are already present and in case return. 354 if (find(carrierIdMismatch) != null) { 355 return false; 356 } 357 // Add the new CarrierIdMismatch at the end of the array, so that the same atom will not be 358 // sent again in future. 359 if (mAtoms.carrierIdMismatch.length == mMaxNumCarrierIdMismatches) { 360 System.arraycopy( 361 mAtoms.carrierIdMismatch, 362 1, 363 mAtoms.carrierIdMismatch, 364 0, 365 mMaxNumCarrierIdMismatches - 1); 366 mAtoms.carrierIdMismatch[mMaxNumCarrierIdMismatches - 1] = carrierIdMismatch; 367 } else { 368 mAtoms.carrierIdMismatch = 369 ArrayUtils.appendElement( 370 CarrierIdMismatch.class, 371 mAtoms.carrierIdMismatch, 372 carrierIdMismatch, 373 true); 374 } 375 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 376 return true; 377 } 378 379 /** Adds IMS registration stats to the storage. */ addImsRegistrationStats(ImsRegistrationStats stats)380 public synchronized void addImsRegistrationStats(ImsRegistrationStats stats) { 381 ImsRegistrationStats existingStats = find(stats); 382 if (existingStats != null) { 383 existingStats.registeredMillis += stats.registeredMillis; 384 existingStats.voiceCapableMillis += stats.voiceCapableMillis; 385 existingStats.voiceAvailableMillis += stats.voiceAvailableMillis; 386 existingStats.smsCapableMillis += stats.smsCapableMillis; 387 existingStats.smsAvailableMillis += stats.smsAvailableMillis; 388 existingStats.videoCapableMillis += stats.videoCapableMillis; 389 existingStats.videoAvailableMillis += stats.videoAvailableMillis; 390 existingStats.utCapableMillis += stats.utCapableMillis; 391 existingStats.utAvailableMillis += stats.utAvailableMillis; 392 existingStats.lastUsedMillis = getWallTimeMillis(); 393 } else { 394 stats.lastUsedMillis = getWallTimeMillis(); 395 mAtoms.imsRegistrationStats = 396 insertAtRandomPlace( 397 mAtoms.imsRegistrationStats, stats, mMaxNumImsRegistrationStats); 398 } 399 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 400 } 401 402 /** Adds IMS registration termination to the storage. */ addImsRegistrationTermination(ImsRegistrationTermination termination)403 public synchronized void addImsRegistrationTermination(ImsRegistrationTermination termination) { 404 ImsRegistrationTermination existingTermination = find(termination); 405 if (existingTermination != null) { 406 existingTermination.count += termination.count; 407 existingTermination.lastUsedMillis = getWallTimeMillis(); 408 } else { 409 termination.lastUsedMillis = getWallTimeMillis(); 410 mAtoms.imsRegistrationTermination = 411 insertAtRandomPlace( 412 mAtoms.imsRegistrationTermination, 413 termination, 414 mMaxNumImsRegistrationTerminations); 415 } 416 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 417 } 418 419 /** 420 * Stores the version of the carrier ID matching table. 421 * 422 * @return true if the version is newer than last available version, false otherwise. 423 */ setCarrierIdTableVersion(int carrierIdTableVersion)424 public synchronized boolean setCarrierIdTableVersion(int carrierIdTableVersion) { 425 if (mAtoms.carrierIdTableVersion < carrierIdTableVersion) { 426 mAtoms.carrierIdTableVersion = carrierIdTableVersion; 427 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 428 return true; 429 } else { 430 return false; 431 } 432 } 433 434 /** Adds a new {@link NetworkRequestsV2} to the storage. */ addNetworkRequestsV2(NetworkRequestsV2 networkRequests)435 public synchronized void addNetworkRequestsV2(NetworkRequestsV2 networkRequests) { 436 NetworkRequestsV2 existingMetrics = find(networkRequests); 437 if (existingMetrics != null) { 438 existingMetrics.requestCount += networkRequests.requestCount; 439 } else { 440 NetworkRequestsV2 newMetrics = new NetworkRequestsV2(); 441 newMetrics.capability = networkRequests.capability; 442 newMetrics.carrierId = networkRequests.carrierId; 443 newMetrics.requestCount = networkRequests.requestCount; 444 mAtoms.networkRequestsV2 = 445 ArrayUtils.appendElement( 446 NetworkRequestsV2.class, mAtoms.networkRequestsV2, newMetrics, true); 447 } 448 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 449 } 450 451 /** Adds a new {@link ImsRegistrationFeatureTagStats} to the storage. */ addImsRegistrationFeatureTagStats( ImsRegistrationFeatureTagStats stats)452 public synchronized void addImsRegistrationFeatureTagStats( 453 ImsRegistrationFeatureTagStats stats) { 454 ImsRegistrationFeatureTagStats existingStats = find(stats); 455 if (existingStats != null) { 456 existingStats.registeredMillis += stats.registeredMillis; 457 } else { 458 mAtoms.imsRegistrationFeatureTagStats = 459 insertAtRandomPlace(mAtoms.imsRegistrationFeatureTagStats, 460 stats, mMaxNumImsRegistrationFeatureStats); 461 } 462 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 463 } 464 465 /** Adds a new {@link RcsClientProvisioningStats} to the storage. */ addRcsClientProvisioningStats(RcsClientProvisioningStats stats)466 public synchronized void addRcsClientProvisioningStats(RcsClientProvisioningStats stats) { 467 RcsClientProvisioningStats existingStats = find(stats); 468 if (existingStats != null) { 469 existingStats.count += 1; 470 } else { 471 mAtoms.rcsClientProvisioningStats = 472 insertAtRandomPlace(mAtoms.rcsClientProvisioningStats, stats, 473 mMaxNumRcsClientProvisioningStats); 474 } 475 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 476 } 477 478 /** Adds a new {@link RcsAcsProvisioningStats} to the storage. */ addRcsAcsProvisioningStats(RcsAcsProvisioningStats stats)479 public synchronized void addRcsAcsProvisioningStats(RcsAcsProvisioningStats stats) { 480 RcsAcsProvisioningStats existingStats = find(stats); 481 if (existingStats != null) { 482 existingStats.count += 1; 483 existingStats.stateTimerMillis += stats.stateTimerMillis; 484 } else { 485 // prevent that wrong count from caller effects total count 486 stats.count = 1; 487 mAtoms.rcsAcsProvisioningStats = 488 insertAtRandomPlace(mAtoms.rcsAcsProvisioningStats, stats, 489 mMaxNumRcsAcsProvisioningStats); 490 } 491 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 492 } 493 494 /** Adds a new {@link SipDelegateStats} to the storage. */ addSipDelegateStats(SipDelegateStats stats)495 public synchronized void addSipDelegateStats(SipDelegateStats stats) { 496 mAtoms.sipDelegateStats = insertAtRandomPlace(mAtoms.sipDelegateStats, stats, 497 mMaxNumSipDelegateStats); 498 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 499 } 500 501 /** Adds a new {@link SipTransportFeatureTagStats} to the storage. */ addSipTransportFeatureTagStats(SipTransportFeatureTagStats stats)502 public synchronized void addSipTransportFeatureTagStats(SipTransportFeatureTagStats stats) { 503 SipTransportFeatureTagStats lastStat = find(stats); 504 if (lastStat != null) { 505 lastStat.associatedMillis += stats.associatedMillis; 506 } else { 507 mAtoms.sipTransportFeatureTagStats = 508 insertAtRandomPlace(mAtoms.sipTransportFeatureTagStats, stats, 509 mMaxNumSipTransportFeatureTagStats); 510 } 511 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 512 } 513 514 /** Adds a new {@link SipMessageResponse} to the storage. */ addSipMessageResponse(SipMessageResponse stats)515 public synchronized void addSipMessageResponse(SipMessageResponse stats) { 516 SipMessageResponse existingStats = find(stats); 517 if (existingStats != null) { 518 existingStats.count += 1; 519 } else { 520 mAtoms.sipMessageResponse = insertAtRandomPlace(mAtoms.sipMessageResponse, stats, 521 mMaxNumSipMessageResponseStats); 522 } 523 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 524 } 525 526 /** Adds a new {@link SipTransportSession} to the storage. */ addCompleteSipTransportSession(SipTransportSession stats)527 public synchronized void addCompleteSipTransportSession(SipTransportSession stats) { 528 SipTransportSession existingStats = find(stats); 529 if (existingStats != null) { 530 existingStats.sessionCount += 1; 531 if (stats.isEndedGracefully) { 532 existingStats.endedGracefullyCount += 1; 533 } 534 } else { 535 mAtoms.sipTransportSession = 536 insertAtRandomPlace(mAtoms.sipTransportSession, stats, 537 mMaxNumSipTransportSessionStats); 538 } 539 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 540 } 541 542 /** Adds a new {@link ImsDedicatedBearerListenerEvent} to the storage. */ addImsDedicatedBearerListenerEvent( ImsDedicatedBearerListenerEvent stats)543 public synchronized void addImsDedicatedBearerListenerEvent( 544 ImsDedicatedBearerListenerEvent stats) { 545 ImsDedicatedBearerListenerEvent existingStats = find(stats); 546 if (existingStats != null) { 547 existingStats.eventCount += 1; 548 } else { 549 mAtoms.imsDedicatedBearerListenerEvent = 550 insertAtRandomPlace(mAtoms.imsDedicatedBearerListenerEvent, 551 stats, mMaxNumDedicatedBearerListenerEventStats); 552 } 553 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 554 } 555 556 /** Adds a new {@link ImsDedicatedBearerEvent} to the storage. */ addImsDedicatedBearerEvent(ImsDedicatedBearerEvent stats)557 public synchronized void addImsDedicatedBearerEvent(ImsDedicatedBearerEvent stats) { 558 ImsDedicatedBearerEvent existingStats = find(stats); 559 if (existingStats != null) { 560 existingStats.count += 1; 561 } else { 562 mAtoms.imsDedicatedBearerEvent = 563 insertAtRandomPlace(mAtoms.imsDedicatedBearerEvent, stats, 564 mMaxNumDedicatedBearerEventStats); 565 } 566 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 567 } 568 569 /** Adds a new {@link ImsRegistrationServiceDescStats} to the storage. */ addImsRegistrationServiceDescStats( ImsRegistrationServiceDescStats stats)570 public synchronized void addImsRegistrationServiceDescStats( 571 ImsRegistrationServiceDescStats stats) { 572 ImsRegistrationServiceDescStats existingStats = find(stats); 573 if (existingStats != null) { 574 existingStats.publishedMillis += stats.publishedMillis; 575 } else { 576 mAtoms.imsRegistrationServiceDescStats = 577 insertAtRandomPlace(mAtoms.imsRegistrationServiceDescStats, 578 stats, mMaxNumImsRegistrationServiceDescStats); 579 } 580 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 581 } 582 583 /** Adds a new {@link UceEventStats} to the storage. */ addUceEventStats(UceEventStats stats)584 public synchronized void addUceEventStats(UceEventStats stats) { 585 UceEventStats existingStats = find(stats); 586 if (existingStats != null) { 587 existingStats.count += 1; 588 } else { 589 mAtoms.uceEventStats = 590 insertAtRandomPlace(mAtoms.uceEventStats, stats, mMaxNumUceEventStats); 591 } 592 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 593 } 594 595 /** Adds a new {@link PresenceNotifyEvent} to the storage. */ addPresenceNotifyEvent(PresenceNotifyEvent stats)596 public synchronized void addPresenceNotifyEvent(PresenceNotifyEvent stats) { 597 PresenceNotifyEvent existingStats = find(stats); 598 if (existingStats != null) { 599 existingStats.rcsCapsCount += stats.rcsCapsCount; 600 existingStats.mmtelCapsCount += stats.mmtelCapsCount; 601 existingStats.noCapsCount += stats.noCapsCount; 602 existingStats.count += stats.count; 603 } else { 604 mAtoms.presenceNotifyEvent = 605 insertAtRandomPlace(mAtoms.presenceNotifyEvent, stats, 606 mMaxNumPresenceNotifyEventStats); 607 } 608 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 609 } 610 611 /** Adds a new {@link GbaEvent} to the storage. */ addGbaEvent(GbaEvent stats)612 public synchronized void addGbaEvent(GbaEvent stats) { 613 GbaEvent existingStats = find(stats); 614 if (existingStats != null) { 615 existingStats.count += 1; 616 } else { 617 mAtoms.gbaEvent = 618 insertAtRandomPlace(mAtoms.gbaEvent, stats, mMaxNumGbaEventStats); 619 } 620 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 621 } 622 623 /** 624 * Sets the unmetered networks bitmask for a given phone id. If the carrier id 625 * doesn't match the existing UnmeteredNetworks' carrier id, the bitmask is 626 * first reset to 0. 627 */ addUnmeteredNetworks( int phoneId, int carrierId, @NetworkTypeBitMask long bitmask)628 public synchronized void addUnmeteredNetworks( 629 int phoneId, int carrierId, @NetworkTypeBitMask long bitmask) { 630 UnmeteredNetworks stats = findUnmeteredNetworks(phoneId); 631 boolean needToSave = true; 632 if (stats == null) { 633 stats = new UnmeteredNetworks(); 634 stats.phoneId = phoneId; 635 stats.carrierId = carrierId; 636 stats.unmeteredNetworksBitmask = bitmask; 637 mAtoms.unmeteredNetworks = 638 ArrayUtils.appendElement( 639 UnmeteredNetworks.class, mAtoms.unmeteredNetworks, stats, true); 640 } else { 641 // Reset the bitmask to 0 if carrier id doesn't match. 642 if (stats.carrierId != carrierId) { 643 stats.carrierId = carrierId; 644 stats.unmeteredNetworksBitmask = 0; 645 } 646 if ((stats.unmeteredNetworksBitmask | bitmask) != stats.unmeteredNetworksBitmask) { 647 stats.unmeteredNetworksBitmask |= bitmask; 648 } else { 649 needToSave = false; 650 } 651 } 652 // Only save if something changes. 653 if (needToSave) { 654 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); 655 } 656 } 657 658 /** 659 * Returns and clears the voice call sessions if last pulled longer than {@code 660 * minIntervalMillis} ago, otherwise returns {@code null}. 661 */ 662 @Nullable getVoiceCallSessions(long minIntervalMillis)663 public synchronized VoiceCallSession[] getVoiceCallSessions(long minIntervalMillis) { 664 if (getWallTimeMillis() - mAtoms.voiceCallSessionPullTimestampMillis > minIntervalMillis) { 665 mAtoms.voiceCallSessionPullTimestampMillis = getWallTimeMillis(); 666 VoiceCallSession[] previousCalls = mAtoms.voiceCallSession; 667 mAtoms.voiceCallSession = new VoiceCallSession[0]; 668 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 669 return previousCalls; 670 } else { 671 return null; 672 } 673 } 674 675 /** 676 * Returns and clears the voice call RAT usages if last pulled longer than {@code 677 * minIntervalMillis} ago, otherwise returns {@code null}. 678 */ 679 @Nullable getVoiceCallRatUsages(long minIntervalMillis)680 public synchronized VoiceCallRatUsage[] getVoiceCallRatUsages(long minIntervalMillis) { 681 if (getWallTimeMillis() - mAtoms.voiceCallRatUsagePullTimestampMillis > minIntervalMillis) { 682 mAtoms.voiceCallRatUsagePullTimestampMillis = getWallTimeMillis(); 683 VoiceCallRatUsage[] previousUsages = mAtoms.voiceCallRatUsage; 684 mVoiceCallRatTracker.clear(); 685 mAtoms.voiceCallRatUsage = new VoiceCallRatUsage[0]; 686 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 687 return previousUsages; 688 } else { 689 return null; 690 } 691 } 692 693 /** 694 * Returns and clears the incoming SMS if last pulled longer than {@code minIntervalMillis} ago, 695 * otherwise returns {@code null}. 696 */ 697 @Nullable getIncomingSms(long minIntervalMillis)698 public synchronized IncomingSms[] getIncomingSms(long minIntervalMillis) { 699 if (getWallTimeMillis() - mAtoms.incomingSmsPullTimestampMillis > minIntervalMillis) { 700 mAtoms.incomingSmsPullTimestampMillis = getWallTimeMillis(); 701 IncomingSms[] previousIncomingSms = mAtoms.incomingSms; 702 mAtoms.incomingSms = new IncomingSms[0]; 703 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 704 return previousIncomingSms; 705 } else { 706 return null; 707 } 708 } 709 710 /** 711 * Returns and clears the outgoing SMS if last pulled longer than {@code minIntervalMillis} ago, 712 * otherwise returns {@code null}. 713 */ 714 @Nullable getOutgoingSms(long minIntervalMillis)715 public synchronized OutgoingSms[] getOutgoingSms(long minIntervalMillis) { 716 if (getWallTimeMillis() - mAtoms.outgoingSmsPullTimestampMillis > minIntervalMillis) { 717 mAtoms.outgoingSmsPullTimestampMillis = getWallTimeMillis(); 718 OutgoingSms[] previousOutgoingSms = mAtoms.outgoingSms; 719 mAtoms.outgoingSms = new OutgoingSms[0]; 720 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 721 return previousOutgoingSms; 722 } else { 723 return null; 724 } 725 } 726 727 /** 728 * Returns and clears the data call session if last pulled longer than {@code minIntervalMillis} 729 * ago, otherwise returns {@code null}. 730 */ 731 @Nullable getDataCallSessions(long minIntervalMillis)732 public synchronized DataCallSession[] getDataCallSessions(long minIntervalMillis) { 733 if (getWallTimeMillis() - mAtoms.dataCallSessionPullTimestampMillis > minIntervalMillis) { 734 mAtoms.dataCallSessionPullTimestampMillis = getWallTimeMillis(); 735 DataCallSession[] previousDataCallSession = mAtoms.dataCallSession; 736 mAtoms.dataCallSession = new DataCallSession[0]; 737 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 738 for (DataCallSession dataCallSession : previousDataCallSession) { 739 // sort to de-correlate any potential pattern for UII concern 740 sortBaseOnArray(dataCallSession.handoverFailureCauses, 741 dataCallSession.handoverFailureRat); 742 } 743 return previousDataCallSession; 744 } else { 745 return null; 746 } 747 } 748 749 /** 750 * Sort the other array base on the natural order of the primary array. Both arrays will be 751 * sorted in-place. 752 * @param primary The primary array to be sorted. 753 * @param other The other array to be sorted in the order of primary array. 754 */ sortBaseOnArray(int[] primary, int[] other)755 private void sortBaseOnArray(int[] primary, int[] other) { 756 if (other.length != primary.length) return; 757 int[] index = IntStream.range(0, primary.length).boxed() 758 .sorted(Comparator.comparingInt(i -> primary[i])) 759 .mapToInt(Integer::intValue) 760 .toArray(); 761 int[] primaryCopy = Arrays.copyOf(primary, primary.length); 762 int[] otherCopy = Arrays.copyOf(other, other.length); 763 for (int i = 0; i < index.length; i++) { 764 primary[i] = primaryCopy[index[i]]; 765 other[i] = otherCopy[index[i]]; 766 } 767 } 768 769 770 /** 771 * Returns and clears the service state durations if last pulled longer than {@code 772 * minIntervalMillis} ago, otherwise returns {@code null}. 773 */ 774 @Nullable getCellularServiceStates(long minIntervalMillis)775 public synchronized CellularServiceState[] getCellularServiceStates(long minIntervalMillis) { 776 if (getWallTimeMillis() - mAtoms.cellularServiceStatePullTimestampMillis 777 > minIntervalMillis) { 778 mAtoms.cellularServiceStatePullTimestampMillis = getWallTimeMillis(); 779 CellularServiceState[] previousStates = mAtoms.cellularServiceState; 780 Arrays.stream(previousStates).forEach(state -> state.lastUsedMillis = 0L); 781 mAtoms.cellularServiceState = new CellularServiceState[0]; 782 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 783 return previousStates; 784 } else { 785 return null; 786 } 787 } 788 789 /** 790 * Returns and clears the service state durations if last pulled longer than {@code 791 * minIntervalMillis} ago, otherwise returns {@code null}. 792 */ 793 @Nullable getCellularDataServiceSwitches( long minIntervalMillis)794 public synchronized CellularDataServiceSwitch[] getCellularDataServiceSwitches( 795 long minIntervalMillis) { 796 if (getWallTimeMillis() - mAtoms.cellularDataServiceSwitchPullTimestampMillis 797 > minIntervalMillis) { 798 mAtoms.cellularDataServiceSwitchPullTimestampMillis = getWallTimeMillis(); 799 CellularDataServiceSwitch[] previousSwitches = mAtoms.cellularDataServiceSwitch; 800 Arrays.stream(previousSwitches) 801 .forEach(serviceSwitch -> serviceSwitch.lastUsedMillis = 0L); 802 mAtoms.cellularDataServiceSwitch = new CellularDataServiceSwitch[0]; 803 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 804 return previousSwitches; 805 } else { 806 return null; 807 } 808 } 809 810 /** 811 * Returns and clears the IMS registration statistics normalized to 24h cycle if last 812 * pulled longer than {@code minIntervalMillis} ago, otherwise returns {@code null}. 813 */ 814 @Nullable getImsRegistrationStats(long minIntervalMillis)815 public synchronized ImsRegistrationStats[] getImsRegistrationStats(long minIntervalMillis) { 816 long intervalMillis = 817 getWallTimeMillis() - mAtoms.imsRegistrationStatsPullTimestampMillis; 818 if (intervalMillis > minIntervalMillis) { 819 mAtoms.imsRegistrationStatsPullTimestampMillis = getWallTimeMillis(); 820 ImsRegistrationStats[] previousStats = mAtoms.imsRegistrationStats; 821 Arrays.stream(previousStats).forEach(stats -> stats.lastUsedMillis = 0L); 822 mAtoms.imsRegistrationStats = new ImsRegistrationStats[0]; 823 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 824 return normalizeData(previousStats, intervalMillis); 825 } else { 826 return null; 827 } 828 } 829 830 /** 831 * Returns and clears the IMS registration terminations if last pulled longer than {@code 832 * minIntervalMillis} ago, otherwise returns {@code null}. 833 */ 834 @Nullable getImsRegistrationTerminations( long minIntervalMillis)835 public synchronized ImsRegistrationTermination[] getImsRegistrationTerminations( 836 long minIntervalMillis) { 837 if (getWallTimeMillis() - mAtoms.imsRegistrationTerminationPullTimestampMillis 838 > minIntervalMillis) { 839 mAtoms.imsRegistrationTerminationPullTimestampMillis = getWallTimeMillis(); 840 ImsRegistrationTermination[] previousTerminations = mAtoms.imsRegistrationTermination; 841 Arrays.stream(previousTerminations) 842 .forEach(termination -> termination.lastUsedMillis = 0L); 843 mAtoms.imsRegistrationTermination = new ImsRegistrationTermination[0]; 844 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 845 return previousTerminations; 846 } else { 847 return null; 848 } 849 } 850 851 /** 852 * Returns and clears the network requests if last pulled longer than {@code 853 * minIntervalMillis} ago, otherwise returns {@code null}. 854 */ 855 @Nullable getNetworkRequestsV2(long minIntervalMillis)856 public synchronized NetworkRequestsV2[] getNetworkRequestsV2(long minIntervalMillis) { 857 if (getWallTimeMillis() - mAtoms.networkRequestsV2PullTimestampMillis > minIntervalMillis) { 858 mAtoms.networkRequestsV2PullTimestampMillis = getWallTimeMillis(); 859 NetworkRequestsV2[] previousNetworkRequests = mAtoms.networkRequestsV2; 860 mAtoms.networkRequestsV2 = new NetworkRequestsV2[0]; 861 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 862 return previousNetworkRequests; 863 } else { 864 return null; 865 } 866 } 867 868 /** 869 * Returns and clears the ImsRegistrationFeatureTagStats if last pulled longer than 870 * {@code minIntervalMillis} ago, otherwise returns {@code null}. 871 */ 872 @Nullable getImsRegistrationFeatureTagStats( long minIntervalMillis)873 public synchronized ImsRegistrationFeatureTagStats[] getImsRegistrationFeatureTagStats( 874 long minIntervalMillis) { 875 long intervalMillis = 876 getWallTimeMillis() - mAtoms.rcsAcsProvisioningStatsPullTimestampMillis; 877 if (intervalMillis > minIntervalMillis) { 878 mAtoms.imsRegistrationFeatureTagStatsPullTimestampMillis = getWallTimeMillis(); 879 ImsRegistrationFeatureTagStats[] previousStats = 880 mAtoms.imsRegistrationFeatureTagStats; 881 mAtoms.imsRegistrationFeatureTagStats = new ImsRegistrationFeatureTagStats[0]; 882 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 883 return previousStats; 884 } else { 885 return null; 886 } 887 } 888 889 /** 890 * Returns and clears the RcsClientProvisioningStats if last pulled longer than {@code 891 * minIntervalMillis} ago, otherwise returns {@code null}. 892 */ 893 @Nullable getRcsClientProvisioningStats( long minIntervalMillis)894 public synchronized RcsClientProvisioningStats[] getRcsClientProvisioningStats( 895 long minIntervalMillis) { 896 if (getWallTimeMillis() - mAtoms.rcsClientProvisioningStatsPullTimestampMillis 897 > minIntervalMillis) { 898 mAtoms.rcsClientProvisioningStatsPullTimestampMillis = getWallTimeMillis(); 899 RcsClientProvisioningStats[] previousStats = mAtoms.rcsClientProvisioningStats; 900 mAtoms.rcsClientProvisioningStats = new RcsClientProvisioningStats[0]; 901 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 902 return previousStats; 903 } else { 904 return null; 905 } 906 } 907 908 /** 909 * Returns and clears the RcsAcsProvisioningStats normalized to 24h cycle if last pulled 910 * longer than {@code minIntervalMillis} ago, otherwise returns {@code null}. 911 */ 912 @Nullable getRcsAcsProvisioningStats( long minIntervalMillis)913 public synchronized RcsAcsProvisioningStats[] getRcsAcsProvisioningStats( 914 long minIntervalMillis) { 915 long intervalMillis = 916 getWallTimeMillis() - mAtoms.rcsAcsProvisioningStatsPullTimestampMillis; 917 if (intervalMillis > minIntervalMillis) { 918 mAtoms.rcsAcsProvisioningStatsPullTimestampMillis = getWallTimeMillis(); 919 RcsAcsProvisioningStats[] previousStats = mAtoms.rcsAcsProvisioningStats; 920 921 for (RcsAcsProvisioningStats stat: previousStats) { 922 // in case pull interval is greater than 24H, normalize it as of one day interval 923 if (intervalMillis > DAY_IN_MILLIS) { 924 stat.stateTimerMillis = normalizeDurationTo24H(stat.stateTimerMillis, 925 intervalMillis); 926 } 927 } 928 929 mAtoms.rcsAcsProvisioningStats = new RcsAcsProvisioningStats[0]; 930 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 931 return previousStats; 932 } else { 933 return null; 934 } 935 } 936 937 /** 938 * Returns and clears the SipDelegateStats if last pulled longer than {@code 939 * minIntervalMillis} ago, otherwise returns {@code null}. 940 */ 941 @Nullable getSipDelegateStats(long minIntervalMillis)942 public synchronized SipDelegateStats[] getSipDelegateStats(long minIntervalMillis) { 943 long intervalMillis = getWallTimeMillis() - mAtoms.sipDelegateStatsPullTimestampMillis; 944 if (intervalMillis > minIntervalMillis) { 945 mAtoms.sipDelegateStatsPullTimestampMillis = getWallTimeMillis(); 946 SipDelegateStats[] previousStats = mAtoms.sipDelegateStats; 947 948 for (SipDelegateStats stat: previousStats) { 949 // in case pull interval is greater than 24H, normalize it as of one day interval 950 if (intervalMillis > DAY_IN_MILLIS) { 951 stat.uptimeMillis = normalizeDurationTo24H(stat.uptimeMillis, 952 intervalMillis); 953 } 954 } 955 956 mAtoms.sipDelegateStats = new SipDelegateStats[0]; 957 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 958 return previousStats; 959 } else { 960 return null; 961 } 962 } 963 964 /** 965 * Returns and clears the SipTransportFeatureTagStats if last pulled longer than {@code 966 * minIntervalMillis} ago, otherwise returns {@code null}. 967 */ 968 @Nullable getSipTransportFeatureTagStats( long minIntervalMillis)969 public synchronized SipTransportFeatureTagStats[] getSipTransportFeatureTagStats( 970 long minIntervalMillis) { 971 long intervalMillis = 972 getWallTimeMillis() - mAtoms.sipTransportFeatureTagStatsPullTimestampMillis; 973 if (intervalMillis > minIntervalMillis) { 974 mAtoms.sipTransportFeatureTagStatsPullTimestampMillis = getWallTimeMillis(); 975 SipTransportFeatureTagStats[] previousStats = mAtoms.sipTransportFeatureTagStats; 976 977 for (SipTransportFeatureTagStats stat: previousStats) { 978 // in case pull interval is greater than 24H, normalize it as of one day interval 979 if (intervalMillis > DAY_IN_MILLIS) { 980 stat.associatedMillis = normalizeDurationTo24H(stat.associatedMillis, 981 intervalMillis); 982 } 983 } 984 985 mAtoms.sipTransportFeatureTagStats = new SipTransportFeatureTagStats[0]; 986 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 987 return previousStats; 988 } else { 989 return null; 990 } 991 } 992 993 /** 994 * Returns and clears the SipMessageResponse if last pulled longer than {@code 995 * minIntervalMillis} ago, otherwise returns {@code null}. 996 */ 997 @Nullable getSipMessageResponse(long minIntervalMillis)998 public synchronized SipMessageResponse[] getSipMessageResponse(long minIntervalMillis) { 999 if (getWallTimeMillis() - mAtoms.sipMessageResponsePullTimestampMillis 1000 > minIntervalMillis) { 1001 mAtoms.sipMessageResponsePullTimestampMillis = getWallTimeMillis(); 1002 SipMessageResponse[] previousStats = 1003 mAtoms.sipMessageResponse; 1004 mAtoms.sipMessageResponse = new SipMessageResponse[0]; 1005 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 1006 return previousStats; 1007 } else { 1008 return null; 1009 } 1010 } 1011 1012 /** 1013 * Returns and clears the SipTransportSession if last pulled longer than {@code 1014 * minIntervalMillis} ago, otherwise returns {@code null}. 1015 */ 1016 @Nullable getSipTransportSession(long minIntervalMillis)1017 public synchronized SipTransportSession[] getSipTransportSession(long minIntervalMillis) { 1018 if (getWallTimeMillis() - mAtoms.sipTransportSessionPullTimestampMillis 1019 > minIntervalMillis) { 1020 mAtoms.sipTransportSessionPullTimestampMillis = getWallTimeMillis(); 1021 SipTransportSession[] previousStats = 1022 mAtoms.sipTransportSession; 1023 mAtoms.sipTransportSession = new SipTransportSession[0]; 1024 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 1025 return previousStats; 1026 } else { 1027 return null; 1028 } 1029 } 1030 1031 /** 1032 * Returns and clears the ImsDedicatedBearerListenerEvent if last pulled longer than {@code 1033 * minIntervalMillis} ago, otherwise returns {@code null}. 1034 */ 1035 @Nullable getImsDedicatedBearerListenerEvent( long minIntervalMillis)1036 public synchronized ImsDedicatedBearerListenerEvent[] getImsDedicatedBearerListenerEvent( 1037 long minIntervalMillis) { 1038 if (getWallTimeMillis() - mAtoms.imsDedicatedBearerListenerEventPullTimestampMillis 1039 > minIntervalMillis) { 1040 mAtoms.imsDedicatedBearerListenerEventPullTimestampMillis = getWallTimeMillis(); 1041 ImsDedicatedBearerListenerEvent[] previousStats = 1042 mAtoms.imsDedicatedBearerListenerEvent; 1043 mAtoms.imsDedicatedBearerListenerEvent = new ImsDedicatedBearerListenerEvent[0]; 1044 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 1045 return previousStats; 1046 } else { 1047 return null; 1048 } 1049 } 1050 1051 /** 1052 * Returns and clears the ImsDedicatedBearerEvent if last pulled longer than {@code 1053 * minIntervalMillis} ago, otherwise returns {@code null}. 1054 */ 1055 @Nullable getImsDedicatedBearerEvent( long minIntervalMillis)1056 public synchronized ImsDedicatedBearerEvent[] getImsDedicatedBearerEvent( 1057 long minIntervalMillis) { 1058 if (getWallTimeMillis() - mAtoms.imsDedicatedBearerEventPullTimestampMillis 1059 > minIntervalMillis) { 1060 mAtoms.imsDedicatedBearerEventPullTimestampMillis = getWallTimeMillis(); 1061 ImsDedicatedBearerEvent[] previousStats = 1062 mAtoms.imsDedicatedBearerEvent; 1063 mAtoms.imsDedicatedBearerEvent = new ImsDedicatedBearerEvent[0]; 1064 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 1065 return previousStats; 1066 } else { 1067 return null; 1068 } 1069 } 1070 1071 /** 1072 * Returns and clears the ImsRegistrationServiceDescStats if last pulled longer than {@code 1073 * minIntervalMillis} ago, otherwise returns {@code null}. 1074 */ 1075 @Nullable getImsRegistrationServiceDescStats(long minIntervalMillis)1076 public synchronized ImsRegistrationServiceDescStats[] getImsRegistrationServiceDescStats(long 1077 minIntervalMillis) { 1078 long intervalMillis = 1079 getWallTimeMillis() - mAtoms.imsRegistrationServiceDescStatsPullTimestampMillis; 1080 if (intervalMillis > minIntervalMillis) { 1081 mAtoms.imsRegistrationServiceDescStatsPullTimestampMillis = getWallTimeMillis(); 1082 ImsRegistrationServiceDescStats[] previousStats = 1083 mAtoms.imsRegistrationServiceDescStats; 1084 1085 for (ImsRegistrationServiceDescStats stat: previousStats) { 1086 // in case pull interval is greater than 24H, normalize it as of one day interval 1087 if (intervalMillis > DAY_IN_MILLIS) { 1088 stat.publishedMillis = normalizeDurationTo24H(stat.publishedMillis, 1089 intervalMillis); 1090 } 1091 } 1092 1093 mAtoms.imsRegistrationServiceDescStats = new ImsRegistrationServiceDescStats[0]; 1094 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 1095 return previousStats; 1096 } else { 1097 return null; 1098 } 1099 } 1100 1101 /** 1102 * Returns and clears the UceEventStats if last pulled longer than {@code 1103 * minIntervalMillis} ago, otherwise returns {@code null}. 1104 */ 1105 @Nullable getUceEventStats(long minIntervalMillis)1106 public synchronized UceEventStats[] getUceEventStats(long minIntervalMillis) { 1107 if (getWallTimeMillis() - mAtoms.uceEventStatsPullTimestampMillis > minIntervalMillis) { 1108 mAtoms.uceEventStatsPullTimestampMillis = getWallTimeMillis(); 1109 UceEventStats[] previousStats = mAtoms.uceEventStats; 1110 mAtoms.uceEventStats = new UceEventStats[0]; 1111 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 1112 return previousStats; 1113 } else { 1114 return null; 1115 } 1116 } 1117 1118 /** 1119 * Returns and clears the PresenceNotifyEvent if last pulled longer than {@code 1120 * minIntervalMillis} ago, otherwise returns {@code null}. 1121 */ 1122 @Nullable getPresenceNotifyEvent(long minIntervalMillis)1123 public synchronized PresenceNotifyEvent[] getPresenceNotifyEvent(long minIntervalMillis) { 1124 if (getWallTimeMillis() - mAtoms.presenceNotifyEventPullTimestampMillis 1125 > minIntervalMillis) { 1126 mAtoms.presenceNotifyEventPullTimestampMillis = getWallTimeMillis(); 1127 PresenceNotifyEvent[] previousStats = mAtoms.presenceNotifyEvent; 1128 mAtoms.presenceNotifyEvent = new PresenceNotifyEvent[0]; 1129 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 1130 return previousStats; 1131 } else { 1132 return null; 1133 } 1134 } 1135 1136 /** 1137 * Returns and clears the GbaEvent if last pulled longer than {@code 1138 * minIntervalMillis} ago, otherwise returns {@code null}. 1139 */ 1140 @Nullable getGbaEvent(long minIntervalMillis)1141 public synchronized GbaEvent[] getGbaEvent(long minIntervalMillis) { 1142 if (getWallTimeMillis() - mAtoms.gbaEventPullTimestampMillis > minIntervalMillis) { 1143 mAtoms.gbaEventPullTimestampMillis = getWallTimeMillis(); 1144 GbaEvent[] previousStats = mAtoms.gbaEvent; 1145 mAtoms.gbaEvent = new GbaEvent[0]; 1146 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 1147 return previousStats; 1148 } else { 1149 return null; 1150 } 1151 } 1152 1153 /** 1154 * Returns the unmetered networks bitmask for a given phone id. Returns 0 if there is 1155 * no existing UnmeteredNetworks for the given phone id or the carrier id doesn't match. 1156 * Existing UnmeteredNetworks is discarded after. 1157 */ getUnmeteredNetworks(int phoneId, int carrierId)1158 public synchronized @NetworkTypeBitMask long getUnmeteredNetworks(int phoneId, int carrierId) { 1159 UnmeteredNetworks existingStats = findUnmeteredNetworks(phoneId); 1160 if (existingStats == null) { 1161 return 0L; 1162 } 1163 @NetworkTypeBitMask 1164 long bitmask = 1165 existingStats.carrierId != carrierId ? 0L : existingStats.unmeteredNetworksBitmask; 1166 mAtoms.unmeteredNetworks = 1167 sanitizeAtoms( 1168 ArrayUtils.removeElement( 1169 UnmeteredNetworks.class, 1170 mAtoms.unmeteredNetworks, 1171 existingStats), 1172 UnmeteredNetworks.class); 1173 saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); 1174 return bitmask; 1175 } 1176 1177 /** Saves {@link PersistAtoms} to a file in private storage immediately. */ flushAtoms()1178 public synchronized void flushAtoms() { 1179 saveAtomsToFile(0); 1180 } 1181 1182 /** Clears atoms for testing purpose. */ clearAtoms()1183 public synchronized void clearAtoms() { 1184 mAtoms = makeNewPersistAtoms(); 1185 saveAtomsToFile(0); 1186 } 1187 1188 /** Loads {@link PersistAtoms} from a file in private storage. */ loadAtomsFromFile()1189 private PersistAtoms loadAtomsFromFile() { 1190 try { 1191 PersistAtoms atoms = 1192 PersistAtoms.parseFrom( 1193 Files.readAllBytes(mContext.getFileStreamPath(FILENAME).toPath())); 1194 // Start from scratch if build changes, since mixing atoms from different builds could 1195 // produce strange results 1196 if (!Build.FINGERPRINT.equals(atoms.buildFingerprint)) { 1197 Rlog.d(TAG, "Build changed"); 1198 return makeNewPersistAtoms(); 1199 } 1200 // check all the fields in case of situations such as OTA or crash during saving 1201 atoms.voiceCallRatUsage = 1202 sanitizeAtoms(atoms.voiceCallRatUsage, VoiceCallRatUsage.class); 1203 atoms.voiceCallSession = 1204 sanitizeAtoms( 1205 atoms.voiceCallSession, 1206 VoiceCallSession.class, 1207 mMaxNumVoiceCallSessions); 1208 atoms.incomingSms = sanitizeAtoms(atoms.incomingSms, IncomingSms.class, mMaxNumSms); 1209 atoms.outgoingSms = sanitizeAtoms(atoms.outgoingSms, OutgoingSms.class, mMaxNumSms); 1210 atoms.carrierIdMismatch = 1211 sanitizeAtoms( 1212 atoms.carrierIdMismatch, 1213 CarrierIdMismatch.class, 1214 mMaxNumCarrierIdMismatches); 1215 atoms.dataCallSession = 1216 sanitizeAtoms( 1217 atoms.dataCallSession, 1218 DataCallSession.class, 1219 mMaxNumDataCallSessions); 1220 atoms.cellularServiceState = 1221 sanitizeAtoms( 1222 atoms.cellularServiceState, 1223 CellularServiceState.class, 1224 mMaxNumCellularServiceStates); 1225 atoms.cellularDataServiceSwitch = 1226 sanitizeAtoms( 1227 atoms.cellularDataServiceSwitch, 1228 CellularDataServiceSwitch.class, 1229 mMaxNumCellularDataSwitches); 1230 atoms.imsRegistrationStats = 1231 sanitizeAtoms( 1232 atoms.imsRegistrationStats, 1233 ImsRegistrationStats.class, 1234 mMaxNumImsRegistrationStats); 1235 atoms.imsRegistrationTermination = 1236 sanitizeAtoms( 1237 atoms.imsRegistrationTermination, 1238 ImsRegistrationTermination.class, 1239 mMaxNumImsRegistrationTerminations); 1240 atoms.networkRequestsV2 = 1241 sanitizeAtoms(atoms.networkRequestsV2, NetworkRequestsV2.class); 1242 atoms.imsRegistrationFeatureTagStats = 1243 sanitizeAtoms( 1244 atoms.imsRegistrationFeatureTagStats, 1245 ImsRegistrationFeatureTagStats.class, 1246 mMaxNumImsRegistrationFeatureStats); 1247 atoms.rcsClientProvisioningStats = 1248 sanitizeAtoms( 1249 atoms.rcsClientProvisioningStats, 1250 RcsClientProvisioningStats.class, 1251 mMaxNumRcsClientProvisioningStats); 1252 atoms.rcsAcsProvisioningStats = 1253 sanitizeAtoms( 1254 atoms.rcsAcsProvisioningStats, 1255 RcsAcsProvisioningStats.class, 1256 mMaxNumRcsAcsProvisioningStats); 1257 atoms.sipDelegateStats = 1258 sanitizeAtoms( 1259 atoms.sipDelegateStats, 1260 SipDelegateStats.class, 1261 mMaxNumSipDelegateStats); 1262 atoms.sipTransportFeatureTagStats = 1263 sanitizeAtoms( 1264 atoms.sipTransportFeatureTagStats, 1265 SipTransportFeatureTagStats.class, 1266 mMaxNumSipTransportFeatureTagStats); 1267 atoms.sipMessageResponse = 1268 sanitizeAtoms( 1269 atoms.sipMessageResponse, 1270 SipMessageResponse.class, 1271 mMaxNumSipMessageResponseStats); 1272 atoms.sipTransportSession = 1273 sanitizeAtoms( 1274 atoms.sipTransportSession, 1275 SipTransportSession.class, 1276 mMaxNumSipTransportSessionStats); 1277 atoms.imsDedicatedBearerListenerEvent = 1278 sanitizeAtoms( 1279 atoms.imsDedicatedBearerListenerEvent, 1280 ImsDedicatedBearerListenerEvent.class, 1281 mMaxNumDedicatedBearerListenerEventStats); 1282 atoms.imsDedicatedBearerEvent = 1283 sanitizeAtoms( 1284 atoms.imsDedicatedBearerEvent, 1285 ImsDedicatedBearerEvent.class, 1286 mMaxNumDedicatedBearerEventStats); 1287 atoms.imsRegistrationServiceDescStats = 1288 sanitizeAtoms( 1289 atoms.imsRegistrationServiceDescStats, 1290 ImsRegistrationServiceDescStats.class, 1291 mMaxNumImsRegistrationServiceDescStats); 1292 atoms.uceEventStats = 1293 sanitizeAtoms( 1294 atoms.uceEventStats, 1295 UceEventStats.class, 1296 mMaxNumUceEventStats); 1297 atoms.presenceNotifyEvent = 1298 sanitizeAtoms( 1299 atoms.presenceNotifyEvent, 1300 PresenceNotifyEvent.class, 1301 mMaxNumPresenceNotifyEventStats); 1302 atoms.gbaEvent = 1303 sanitizeAtoms( 1304 atoms.gbaEvent, 1305 GbaEvent.class, 1306 mMaxNumGbaEventStats); 1307 atoms.unmeteredNetworks = 1308 sanitizeAtoms( 1309 atoms.unmeteredNetworks, 1310 UnmeteredNetworks.class 1311 ); 1312 1313 // out of caution, sanitize also the timestamps 1314 atoms.voiceCallRatUsagePullTimestampMillis = 1315 sanitizeTimestamp(atoms.voiceCallRatUsagePullTimestampMillis); 1316 atoms.voiceCallSessionPullTimestampMillis = 1317 sanitizeTimestamp(atoms.voiceCallSessionPullTimestampMillis); 1318 atoms.incomingSmsPullTimestampMillis = 1319 sanitizeTimestamp(atoms.incomingSmsPullTimestampMillis); 1320 atoms.outgoingSmsPullTimestampMillis = 1321 sanitizeTimestamp(atoms.outgoingSmsPullTimestampMillis); 1322 atoms.dataCallSessionPullTimestampMillis = 1323 sanitizeTimestamp(atoms.dataCallSessionPullTimestampMillis); 1324 atoms.cellularServiceStatePullTimestampMillis = 1325 sanitizeTimestamp(atoms.cellularServiceStatePullTimestampMillis); 1326 atoms.cellularDataServiceSwitchPullTimestampMillis = 1327 sanitizeTimestamp(atoms.cellularDataServiceSwitchPullTimestampMillis); 1328 atoms.imsRegistrationStatsPullTimestampMillis = 1329 sanitizeTimestamp(atoms.imsRegistrationStatsPullTimestampMillis); 1330 atoms.imsRegistrationTerminationPullTimestampMillis = 1331 sanitizeTimestamp(atoms.imsRegistrationTerminationPullTimestampMillis); 1332 atoms.networkRequestsV2PullTimestampMillis = 1333 sanitizeTimestamp(atoms.networkRequestsV2PullTimestampMillis); 1334 atoms.imsRegistrationFeatureTagStatsPullTimestampMillis = 1335 sanitizeTimestamp(atoms.imsRegistrationFeatureTagStatsPullTimestampMillis); 1336 atoms.rcsClientProvisioningStatsPullTimestampMillis = 1337 sanitizeTimestamp(atoms.rcsClientProvisioningStatsPullTimestampMillis); 1338 atoms.rcsAcsProvisioningStatsPullTimestampMillis = 1339 sanitizeTimestamp(atoms.rcsAcsProvisioningStatsPullTimestampMillis); 1340 atoms.sipDelegateStatsPullTimestampMillis = 1341 sanitizeTimestamp(atoms.sipDelegateStatsPullTimestampMillis); 1342 atoms.sipTransportFeatureTagStatsPullTimestampMillis = 1343 sanitizeTimestamp(atoms.sipTransportFeatureTagStatsPullTimestampMillis); 1344 atoms.sipMessageResponsePullTimestampMillis = 1345 sanitizeTimestamp(atoms.sipMessageResponsePullTimestampMillis); 1346 atoms.sipTransportSessionPullTimestampMillis = 1347 sanitizeTimestamp(atoms.sipTransportSessionPullTimestampMillis); 1348 atoms.imsDedicatedBearerListenerEventPullTimestampMillis = 1349 sanitizeTimestamp(atoms.imsDedicatedBearerListenerEventPullTimestampMillis); 1350 atoms.imsDedicatedBearerEventPullTimestampMillis = 1351 sanitizeTimestamp(atoms.imsDedicatedBearerEventPullTimestampMillis); 1352 atoms.imsRegistrationServiceDescStatsPullTimestampMillis = 1353 sanitizeTimestamp(atoms.imsRegistrationServiceDescStatsPullTimestampMillis); 1354 atoms.uceEventStatsPullTimestampMillis = 1355 sanitizeTimestamp(atoms.uceEventStatsPullTimestampMillis); 1356 atoms.presenceNotifyEventPullTimestampMillis = 1357 sanitizeTimestamp(atoms.presenceNotifyEventPullTimestampMillis); 1358 atoms.gbaEventPullTimestampMillis = 1359 sanitizeTimestamp(atoms.gbaEventPullTimestampMillis); 1360 1361 return atoms; 1362 } catch (NoSuchFileException e) { 1363 Rlog.d(TAG, "PersistAtoms file not found"); 1364 } catch (IOException | NullPointerException e) { 1365 Rlog.e(TAG, "cannot load/parse PersistAtoms", e); 1366 } 1367 return makeNewPersistAtoms(); 1368 } 1369 1370 /** 1371 * Posts message to save a copy of {@link PersistAtoms} to a file after a delay or immediately. 1372 * 1373 * <p>The delay is introduced to avoid too frequent operations to disk, which would negatively 1374 * impact the power consumption. 1375 */ saveAtomsToFile(int delayMillis)1376 private synchronized void saveAtomsToFile(int delayMillis) { 1377 mHandler.removeCallbacks(mSaveRunnable); 1378 if (delayMillis > 0 && !mSaveImmediately) { 1379 if (mHandler.postDelayed(mSaveRunnable, delayMillis)) { 1380 return; 1381 } 1382 } 1383 // In case of error posting the event or if delay is 0, save immediately 1384 saveAtomsToFileNow(); 1385 } 1386 1387 /** Saves a copy of {@link PersistAtoms} to a file in private storage. */ saveAtomsToFileNow()1388 private synchronized void saveAtomsToFileNow() { 1389 try (FileOutputStream stream = mContext.openFileOutput(FILENAME, Context.MODE_PRIVATE)) { 1390 stream.write(PersistAtoms.toByteArray(mAtoms)); 1391 } catch (IOException e) { 1392 Rlog.e(TAG, "cannot save PersistAtoms", e); 1393 } 1394 } 1395 1396 /** 1397 * Returns the service state that has the same dimension values with the given one, or {@code 1398 * null} if it does not exist. 1399 */ find(CellularServiceState key)1400 private @Nullable CellularServiceState find(CellularServiceState key) { 1401 for (CellularServiceState state : mAtoms.cellularServiceState) { 1402 if (state.voiceRat == key.voiceRat 1403 && state.dataRat == key.dataRat 1404 && state.voiceRoamingType == key.voiceRoamingType 1405 && state.dataRoamingType == key.dataRoamingType 1406 && state.isEndc == key.isEndc 1407 && state.simSlotIndex == key.simSlotIndex 1408 && state.isMultiSim == key.isMultiSim 1409 && state.carrierId == key.carrierId 1410 && state.isEmergencyOnly == key.isEmergencyOnly) { 1411 return state; 1412 } 1413 } 1414 return null; 1415 } 1416 1417 /** 1418 * Returns the data service switch that has the same dimension values with the given one, or 1419 * {@code null} if it does not exist. 1420 */ find(CellularDataServiceSwitch key)1421 private @Nullable CellularDataServiceSwitch find(CellularDataServiceSwitch key) { 1422 for (CellularDataServiceSwitch serviceSwitch : mAtoms.cellularDataServiceSwitch) { 1423 if (serviceSwitch.ratFrom == key.ratFrom 1424 && serviceSwitch.ratTo == key.ratTo 1425 && serviceSwitch.simSlotIndex == key.simSlotIndex 1426 && serviceSwitch.isMultiSim == key.isMultiSim 1427 && serviceSwitch.carrierId == key.carrierId) { 1428 return serviceSwitch; 1429 } 1430 } 1431 return null; 1432 } 1433 1434 /** 1435 * Returns the carrier ID mismatch event that has the same dimension values with the given one, 1436 * or {@code null} if it does not exist. 1437 */ find(CarrierIdMismatch key)1438 private @Nullable CarrierIdMismatch find(CarrierIdMismatch key) { 1439 for (CarrierIdMismatch mismatch : mAtoms.carrierIdMismatch) { 1440 if (mismatch.mccMnc.equals(key.mccMnc) 1441 && mismatch.gid1.equals(key.gid1) 1442 && mismatch.spn.equals(key.spn) 1443 && mismatch.pnn.equals(key.pnn)) { 1444 return mismatch; 1445 } 1446 } 1447 return null; 1448 } 1449 1450 /** 1451 * Returns the IMS registration stats that has the same dimension values with the given one, or 1452 * {@code null} if it does not exist. 1453 */ find(ImsRegistrationStats key)1454 private @Nullable ImsRegistrationStats find(ImsRegistrationStats key) { 1455 for (ImsRegistrationStats stats : mAtoms.imsRegistrationStats) { 1456 if (stats.carrierId == key.carrierId 1457 && stats.simSlotIndex == key.simSlotIndex 1458 && stats.rat == key.rat) { 1459 return stats; 1460 } 1461 } 1462 return null; 1463 } 1464 1465 /** 1466 * Returns the IMS registration termination that has the same dimension values with the given 1467 * one, or {@code null} if it does not exist. 1468 */ find(ImsRegistrationTermination key)1469 private @Nullable ImsRegistrationTermination find(ImsRegistrationTermination key) { 1470 for (ImsRegistrationTermination termination : mAtoms.imsRegistrationTermination) { 1471 if (termination.carrierId == key.carrierId 1472 && termination.isMultiSim == key.isMultiSim 1473 && termination.ratAtEnd == key.ratAtEnd 1474 && termination.setupFailed == key.setupFailed 1475 && termination.reasonCode == key.reasonCode 1476 && termination.extraCode == key.extraCode 1477 && termination.extraMessage.equals(key.extraMessage)) { 1478 return termination; 1479 } 1480 } 1481 return null; 1482 } 1483 1484 /** 1485 * Returns the network requests event that has the same carrier id and capability as the given 1486 * one, or {@code null} if it does not exist. 1487 */ find(NetworkRequestsV2 key)1488 private @Nullable NetworkRequestsV2 find(NetworkRequestsV2 key) { 1489 for (NetworkRequestsV2 item : mAtoms.networkRequestsV2) { 1490 if (item.carrierId == key.carrierId && item.capability == key.capability) { 1491 return item; 1492 } 1493 } 1494 return null; 1495 } 1496 1497 /** 1498 * Returns the index of data call session that has the same random dimension as the given one, 1499 * or -1 if it does not exist. 1500 */ findIndex(DataCallSession key)1501 private int findIndex(DataCallSession key) { 1502 for (int i = 0; i < mAtoms.dataCallSession.length; i++) { 1503 if (mAtoms.dataCallSession[i].dimension == key.dimension) { 1504 return i; 1505 } 1506 } 1507 return -1; 1508 } 1509 /** 1510 * Returns the Dedicated Bearer Listener event that has the same carrier id, slot id, rat, qci 1511 * and established state as the given one, or {@code null} if it does not exist. 1512 */ find(ImsDedicatedBearerListenerEvent key)1513 private @Nullable ImsDedicatedBearerListenerEvent find(ImsDedicatedBearerListenerEvent key) { 1514 for (ImsDedicatedBearerListenerEvent stats : mAtoms.imsDedicatedBearerListenerEvent) { 1515 if (stats.carrierId == key.carrierId 1516 && stats.slotId == key.slotId 1517 && stats.ratAtEnd == key.ratAtEnd 1518 && stats.qci == key.qci 1519 && stats.dedicatedBearerEstablished == key.dedicatedBearerEstablished) { 1520 return stats; 1521 } 1522 } 1523 return null; 1524 } 1525 1526 /** 1527 * Returns the Dedicated Bearer event that has the same carrier id, slot id, rat, 1528 * qci, bearer state, local/remote connection and exsting listener as the given one, 1529 * or {@code null} if it does not exist. 1530 */ find(ImsDedicatedBearerEvent key)1531 private @Nullable ImsDedicatedBearerEvent find(ImsDedicatedBearerEvent key) { 1532 for (ImsDedicatedBearerEvent stats : mAtoms.imsDedicatedBearerEvent) { 1533 if (stats.carrierId == key.carrierId 1534 && stats.slotId == key.slotId 1535 && stats.ratAtEnd == key.ratAtEnd 1536 && stats.qci == key.qci 1537 && stats.bearerState == key.bearerState 1538 && stats.localConnectionInfoReceived == key.localConnectionInfoReceived 1539 && stats.remoteConnectionInfoReceived == key.remoteConnectionInfoReceived 1540 && stats.hasListeners == key.hasListeners) { 1541 return stats; 1542 } 1543 } 1544 return null; 1545 } 1546 1547 /** 1548 * Returns the Registration Feature Tag that has the same carrier id, slot id, 1549 * feature tag name or custom feature tag name and registration tech as the given one, 1550 * or {@code null} if it does not exist. 1551 */ find(ImsRegistrationFeatureTagStats key)1552 private @Nullable ImsRegistrationFeatureTagStats find(ImsRegistrationFeatureTagStats key) { 1553 for (ImsRegistrationFeatureTagStats stats : mAtoms.imsRegistrationFeatureTagStats) { 1554 if (stats.carrierId == key.carrierId 1555 && stats.slotId == key.slotId 1556 && stats.featureTagName == key.featureTagName 1557 && stats.registrationTech == key.registrationTech) { 1558 return stats; 1559 } 1560 } 1561 return null; 1562 } 1563 1564 /** 1565 * Returns Client Provisioning that has the same carrier id, slot id and event as the given 1566 * one, or {@code null} if it does not exist. 1567 */ find(RcsClientProvisioningStats key)1568 private @Nullable RcsClientProvisioningStats find(RcsClientProvisioningStats key) { 1569 for (RcsClientProvisioningStats stats : mAtoms.rcsClientProvisioningStats) { 1570 if (stats.carrierId == key.carrierId 1571 && stats.slotId == key.slotId 1572 && stats.event == key.event) { 1573 return stats; 1574 } 1575 } 1576 return null; 1577 } 1578 1579 /** 1580 * Returns ACS Provisioning that has the same carrier id, slot id, response code, response type 1581 * and SR supported as the given one, or {@code null} if it does not exist. 1582 */ find(RcsAcsProvisioningStats key)1583 private @Nullable RcsAcsProvisioningStats find(RcsAcsProvisioningStats key) { 1584 for (RcsAcsProvisioningStats stats : mAtoms.rcsAcsProvisioningStats) { 1585 if (stats.carrierId == key.carrierId 1586 && stats.slotId == key.slotId 1587 && stats.responseCode == key.responseCode 1588 && stats.responseType == key.responseType 1589 && stats.isSingleRegistrationEnabled == key.isSingleRegistrationEnabled) { 1590 return stats; 1591 } 1592 } 1593 return null; 1594 } 1595 1596 /** 1597 * Returns Sip Message Response that has the same carrier id, slot id, method, response, 1598 * direction and error as the given one, or {@code null} if it does not exist. 1599 */ find(SipMessageResponse key)1600 private @Nullable SipMessageResponse find(SipMessageResponse key) { 1601 for (SipMessageResponse stats : mAtoms.sipMessageResponse) { 1602 if (stats.carrierId == key.carrierId 1603 && stats.slotId == key.slotId 1604 && stats.sipMessageMethod == key.sipMessageMethod 1605 && stats.sipMessageResponse == key.sipMessageResponse 1606 && stats.sipMessageDirection == key.sipMessageDirection 1607 && stats.messageError == key.messageError) { 1608 return stats; 1609 } 1610 } 1611 return null; 1612 } 1613 1614 /** 1615 * Returns Sip Transport Session that has the same carrier id, slot id, method, direction and 1616 * response as the given one, or {@code null} if it does not exist. 1617 */ find(SipTransportSession key)1618 private @Nullable SipTransportSession find(SipTransportSession key) { 1619 for (SipTransportSession stats : mAtoms.sipTransportSession) { 1620 if (stats.carrierId == key.carrierId 1621 && stats.slotId == key.slotId 1622 && stats.sessionMethod == key.sessionMethod 1623 && stats.sipMessageDirection == key.sipMessageDirection 1624 && stats.sipResponse == key.sipResponse) { 1625 return stats; 1626 } 1627 } 1628 return null; 1629 } 1630 1631 /** 1632 * Returns Registration Service Desc Stats that has the same carrier id, slot id, service id or 1633 * custom service id, service id version and registration tech as the given one, 1634 * or {@code null} if it does not exist. 1635 */ find(ImsRegistrationServiceDescStats key)1636 private @Nullable ImsRegistrationServiceDescStats find(ImsRegistrationServiceDescStats key) { 1637 for (ImsRegistrationServiceDescStats stats : mAtoms.imsRegistrationServiceDescStats) { 1638 if (stats.carrierId == key.carrierId 1639 && stats.slotId == key.slotId 1640 && stats.serviceIdName == key.serviceIdName 1641 && stats.serviceIdVersion == key.serviceIdVersion 1642 && stats.registrationTech == key.registrationTech) { 1643 return stats; 1644 } 1645 } 1646 return null; 1647 } 1648 1649 /** 1650 * Returns UCE Event Stats that has the same carrier id, slot id, event result, command code and 1651 * network response as the given one, or {@code null} if it does not exist. 1652 */ find(UceEventStats key)1653 private @Nullable UceEventStats find(UceEventStats key) { 1654 for (UceEventStats stats : mAtoms.uceEventStats) { 1655 if (stats.carrierId == key.carrierId 1656 && stats.slotId == key.slotId 1657 && stats.type == key.type 1658 && stats.successful == key.successful 1659 && stats.commandCode == key.commandCode 1660 && stats.networkResponse == key.networkResponse) { 1661 return stats; 1662 } 1663 } 1664 return null; 1665 } 1666 1667 /** 1668 * Returns Presence Notify Event that has the same carrier id, slot id, reason and body in 1669 * response as the given one, or {@code null} if it does not exist. 1670 */ find(PresenceNotifyEvent key)1671 private @Nullable PresenceNotifyEvent find(PresenceNotifyEvent key) { 1672 for (PresenceNotifyEvent stats : mAtoms.presenceNotifyEvent) { 1673 if (stats.carrierId == key.carrierId 1674 && stats.slotId == key.slotId 1675 && stats.reason == key.reason 1676 && stats.contentBodyReceived == key.contentBodyReceived) { 1677 return stats; 1678 } 1679 } 1680 return null; 1681 } 1682 1683 /** 1684 * Returns GBA Event that has the same carrier id, slot id, result of operation and fail reason 1685 * as the given one, or {@code null} if it does not exist. 1686 */ find(GbaEvent key)1687 private @Nullable GbaEvent find(GbaEvent key) { 1688 for (GbaEvent stats : mAtoms.gbaEvent) { 1689 if (stats.carrierId == key.carrierId 1690 && stats.slotId == key.slotId 1691 && stats.successful == key.successful 1692 && stats.failedReason == key.failedReason) { 1693 return stats; 1694 } 1695 } 1696 return null; 1697 } 1698 1699 /** 1700 * Returns Sip Transport Feature Tag Stats that has the same carrier id, slot id, feature tag 1701 * name, deregister reason, denied reason and feature tag name or custom feature tag name as 1702 * the given one, or {@code null} if it does not exist. 1703 */ find(SipTransportFeatureTagStats key)1704 private @Nullable SipTransportFeatureTagStats find(SipTransportFeatureTagStats key) { 1705 for (SipTransportFeatureTagStats stat : mAtoms.sipTransportFeatureTagStats) { 1706 if (stat.carrierId == key.carrierId 1707 && stat.slotId == key.slotId 1708 && stat.featureTagName == key.featureTagName 1709 && stat.sipTransportDeregisteredReason == key.sipTransportDeregisteredReason 1710 && stat.sipTransportDeniedReason == key.sipTransportDeniedReason) { 1711 return stat; 1712 } 1713 } 1714 return null; 1715 } 1716 1717 /** Returns the UnmeteredNetworks given a phone id. */ findUnmeteredNetworks(int phoneId)1718 private @Nullable UnmeteredNetworks findUnmeteredNetworks(int phoneId) { 1719 for (UnmeteredNetworks unmeteredNetworks : mAtoms.unmeteredNetworks) { 1720 if (unmeteredNetworks.phoneId == phoneId) { 1721 return unmeteredNetworks; 1722 } 1723 } 1724 return null; 1725 } 1726 1727 /** 1728 * Inserts a new element in a random position in an array with a maximum size. 1729 * 1730 * <p>If the array is full, merge with existing item if possible or replace one item randomly. 1731 */ insertAtRandomPlace(T[] storage, T instance, int maxLength)1732 private static <T> T[] insertAtRandomPlace(T[] storage, T instance, int maxLength) { 1733 final int newLength = storage.length + 1; 1734 final boolean arrayFull = (newLength > maxLength); 1735 T[] result = Arrays.copyOf(storage, arrayFull ? maxLength : newLength); 1736 if (newLength == 1) { 1737 result[0] = instance; 1738 } else if (arrayFull) { 1739 if (instance instanceof OutgoingSms || instance instanceof IncomingSms) { 1740 mergeSmsOrEvictInFullStorage(result, instance); 1741 } else { 1742 result[findItemToEvict(storage)] = instance; 1743 } 1744 } else { 1745 // insert at random place (by moving the item at the random place to the end) 1746 int insertAt = sRandom.nextInt(newLength); 1747 result[newLength - 1] = result[insertAt]; 1748 result[insertAt] = instance; 1749 } 1750 return result; 1751 } 1752 1753 /** 1754 * Merge new sms in a full storage. 1755 * 1756 * <p>If new sms is similar to old sms, merge them. 1757 * If not, merge 2 old similar sms and add the new sms. 1758 * If not, replace old sms with the lowest count. 1759 */ mergeSmsOrEvictInFullStorage(T[] storage, T instance)1760 private static <T> void mergeSmsOrEvictInFullStorage(T[] storage, T instance) { 1761 // key: hashCode, value: smsIndex 1762 SparseIntArray map = new SparseIntArray(); 1763 int smsIndex1 = -1; 1764 int smsIndex2 = -1; 1765 int indexLowestCount = -1; 1766 int minCount = Integer.MAX_VALUE; 1767 1768 for (int i = 0; i < storage.length; i++) { 1769 // If the new SMS can be merged to an existing item, merge it and return immediately. 1770 if (areSmsMergeable(storage[i], instance)) { 1771 storage[i] = mergeSms(storage[i], instance); 1772 return; 1773 } 1774 1775 // Keep sms index with lowest count to evict, in case we cannot merge any 2 messages. 1776 int smsCount = getSmsCount(storage[i]); 1777 if (smsCount < minCount) { 1778 indexLowestCount = i; 1779 minCount = smsCount; 1780 } 1781 1782 // Find any 2 messages in the storage that can be merged together. 1783 if (smsIndex1 != -1) { 1784 int smsHashCode = getSmsHashCode(storage[i]); 1785 if (map.indexOfKey(smsHashCode) < 0) { 1786 map.append(smsHashCode, i); 1787 } else { 1788 smsIndex1 = map.get(smsHashCode); 1789 smsIndex2 = i; 1790 } 1791 } 1792 } 1793 1794 // Merge 2 similar old sms and add the new sms 1795 if (smsIndex1 != -1) { 1796 storage[smsIndex1] = mergeSms(storage[smsIndex1], storage[smsIndex2]); 1797 storage[smsIndex2] = instance; 1798 return; 1799 } 1800 1801 // Or replace old sms that has the lowest count 1802 storage[indexLowestCount] = instance; 1803 return; 1804 } 1805 getSmsHashCode(T sms)1806 private static <T> int getSmsHashCode(T sms) { 1807 return sms instanceof OutgoingSms 1808 ? ((OutgoingSms) sms).hashCode : ((IncomingSms) sms).hashCode; 1809 } 1810 getSmsCount(T sms)1811 private static <T> int getSmsCount(T sms) { 1812 return sms instanceof OutgoingSms 1813 ? ((OutgoingSms) sms).count : ((IncomingSms) sms).count; 1814 } 1815 1816 /** Compares 2 SMS hash codes to check if they can be clubbed together in the metrics. */ areSmsMergeable(T instance1, T instance2)1817 private static <T> boolean areSmsMergeable(T instance1, T instance2) { 1818 return getSmsHashCode(instance1) == getSmsHashCode(instance2); 1819 } 1820 1821 /** Merges sms2 data on top of sms1 and returns the merged value. */ mergeSms(T sms1, T sms2)1822 private static <T> T mergeSms(T sms1, T sms2) { 1823 if (sms1 instanceof OutgoingSms) { 1824 OutgoingSms tSms1 = (OutgoingSms) sms1; 1825 OutgoingSms tSms2 = (OutgoingSms) sms2; 1826 tSms1.intervalMillis = (tSms1.intervalMillis * tSms1.count 1827 + tSms2.intervalMillis * tSms2.count) / (tSms1.count + tSms2.count); 1828 tSms1.count += tSms2.count; 1829 } else if (sms1 instanceof IncomingSms) { 1830 IncomingSms tSms1 = (IncomingSms) sms1; 1831 IncomingSms tSms2 = (IncomingSms) sms2; 1832 tSms1.count += tSms2.count; 1833 } 1834 return sms1; 1835 } 1836 1837 /** Returns index of the item suitable for eviction when the array is full. */ findItemToEvict(T[] array)1838 private static <T> int findItemToEvict(T[] array) { 1839 if (array instanceof CellularServiceState[]) { 1840 // Evict the item that was used least recently 1841 CellularServiceState[] arr = (CellularServiceState[]) array; 1842 return IntStream.range(0, arr.length) 1843 .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j) 1844 .getAsInt(); 1845 } 1846 1847 if (array instanceof CellularDataServiceSwitch[]) { 1848 // Evict the item that was used least recently 1849 CellularDataServiceSwitch[] arr = (CellularDataServiceSwitch[]) array; 1850 return IntStream.range(0, arr.length) 1851 .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j) 1852 .getAsInt(); 1853 } 1854 1855 if (array instanceof ImsRegistrationStats[]) { 1856 // Evict the item that was used least recently 1857 ImsRegistrationStats[] arr = (ImsRegistrationStats[]) array; 1858 return IntStream.range(0, arr.length) 1859 .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j) 1860 .getAsInt(); 1861 } 1862 1863 if (array instanceof ImsRegistrationTermination[]) { 1864 // Evict the item that was used least recently 1865 ImsRegistrationTermination[] arr = (ImsRegistrationTermination[]) array; 1866 return IntStream.range(0, arr.length) 1867 .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j) 1868 .getAsInt(); 1869 } 1870 1871 if (array instanceof VoiceCallSession[]) { 1872 // For voice calls, try to keep emergency calls over regular calls. 1873 VoiceCallSession[] arr = (VoiceCallSession[]) array; 1874 int[] nonEmergencyCallIndexes = IntStream.range(0, arr.length) 1875 .filter(i -> !arr[i].isEmergency) 1876 .toArray(); 1877 if (nonEmergencyCallIndexes.length > 0) { 1878 return nonEmergencyCallIndexes[sRandom.nextInt(nonEmergencyCallIndexes.length)]; 1879 } 1880 // If all calls in the storage are emergency calls, proceed with default case 1881 // even if the new call is not an emergency call. 1882 } 1883 1884 return sRandom.nextInt(array.length); 1885 } 1886 1887 /** Sanitizes the loaded array of atoms to avoid null values. */ sanitizeAtoms(T[] array, Class<T> cl)1888 private <T> T[] sanitizeAtoms(T[] array, Class<T> cl) { 1889 return ArrayUtils.emptyIfNull(array, cl); 1890 } 1891 1892 /** Sanitizes the loaded array of atoms loaded to avoid null values and enforce max length. */ sanitizeAtoms(T[] array, Class<T> cl, int maxLength)1893 private <T> T[] sanitizeAtoms(T[] array, Class<T> cl, int maxLength) { 1894 array = sanitizeAtoms(array, cl); 1895 if (array.length > maxLength) { 1896 return Arrays.copyOf(array, maxLength); 1897 } 1898 return array; 1899 } 1900 1901 /** Sanitizes the timestamp of the last pull loaded from persistent storage. */ sanitizeTimestamp(long timestamp)1902 private long sanitizeTimestamp(long timestamp) { 1903 return timestamp <= 0L ? getWallTimeMillis() : timestamp; 1904 } 1905 1906 /** 1907 * Returns {@link ImsRegistrationStats} array with durations normalized to 24 hours 1908 * depending on the interval. 1909 */ normalizeData(ImsRegistrationStats[] stats, long intervalMillis)1910 private ImsRegistrationStats[] normalizeData(ImsRegistrationStats[] stats, 1911 long intervalMillis) { 1912 for (int i = 0; i < stats.length; i++) { 1913 stats[i].registeredMillis = 1914 normalizeDurationTo24H(stats[i].registeredMillis, intervalMillis); 1915 stats[i].voiceCapableMillis = 1916 normalizeDurationTo24H(stats[i].voiceCapableMillis, intervalMillis); 1917 stats[i].voiceAvailableMillis = 1918 normalizeDurationTo24H(stats[i].voiceAvailableMillis, intervalMillis); 1919 stats[i].smsCapableMillis = 1920 normalizeDurationTo24H(stats[i].smsCapableMillis, intervalMillis); 1921 stats[i].smsAvailableMillis = 1922 normalizeDurationTo24H(stats[i].smsAvailableMillis, intervalMillis); 1923 stats[i].videoCapableMillis = 1924 normalizeDurationTo24H(stats[i].videoCapableMillis, intervalMillis); 1925 stats[i].videoAvailableMillis = 1926 normalizeDurationTo24H(stats[i].videoAvailableMillis, intervalMillis); 1927 stats[i].utCapableMillis = 1928 normalizeDurationTo24H(stats[i].utCapableMillis, intervalMillis); 1929 stats[i].utAvailableMillis = 1930 normalizeDurationTo24H(stats[i].utAvailableMillis, intervalMillis); 1931 } 1932 return stats; 1933 } 1934 1935 /** Returns a duration normalized to 24 hours. */ normalizeDurationTo24H(long timeInMillis, long intervalMillis)1936 private long normalizeDurationTo24H(long timeInMillis, long intervalMillis) { 1937 long interval = intervalMillis < 1000 ? 1 : intervalMillis / 1000; 1938 return ((timeInMillis / 1000) * (DAY_IN_MILLIS / 1000) / interval) * 1000; 1939 } 1940 1941 /** Returns an empty PersistAtoms with pull timestamp set to current time. */ 1942 private PersistAtoms makeNewPersistAtoms() { 1943 PersistAtoms atoms = new PersistAtoms(); 1944 // allow pulling only after some time so data are sufficiently aggregated 1945 long currentTime = getWallTimeMillis(); 1946 atoms.buildFingerprint = Build.FINGERPRINT; 1947 atoms.voiceCallRatUsagePullTimestampMillis = currentTime; 1948 atoms.voiceCallSessionPullTimestampMillis = currentTime; 1949 atoms.incomingSmsPullTimestampMillis = currentTime; 1950 atoms.outgoingSmsPullTimestampMillis = currentTime; 1951 atoms.carrierIdTableVersion = TelephonyManager.UNKNOWN_CARRIER_ID_LIST_VERSION; 1952 atoms.dataCallSessionPullTimestampMillis = currentTime; 1953 atoms.cellularServiceStatePullTimestampMillis = currentTime; 1954 atoms.cellularDataServiceSwitchPullTimestampMillis = currentTime; 1955 atoms.imsRegistrationStatsPullTimestampMillis = currentTime; 1956 atoms.imsRegistrationTerminationPullTimestampMillis = currentTime; 1957 atoms.networkRequestsPullTimestampMillis = currentTime; 1958 atoms.networkRequestsV2PullTimestampMillis = currentTime; 1959 atoms.imsRegistrationFeatureTagStatsPullTimestampMillis = currentTime; 1960 atoms.rcsClientProvisioningStatsPullTimestampMillis = currentTime; 1961 atoms.rcsAcsProvisioningStatsPullTimestampMillis = currentTime; 1962 atoms.sipDelegateStatsPullTimestampMillis = currentTime; 1963 atoms.sipTransportFeatureTagStatsPullTimestampMillis = currentTime; 1964 atoms.sipMessageResponsePullTimestampMillis = currentTime; 1965 atoms.sipTransportSessionPullTimestampMillis = currentTime; 1966 atoms.imsDedicatedBearerListenerEventPullTimestampMillis = currentTime; 1967 atoms.imsDedicatedBearerEventPullTimestampMillis = currentTime; 1968 atoms.imsRegistrationServiceDescStatsPullTimestampMillis = currentTime; 1969 atoms.uceEventStatsPullTimestampMillis = currentTime; 1970 atoms.presenceNotifyEventPullTimestampMillis = currentTime; 1971 atoms.gbaEventPullTimestampMillis = currentTime; 1972 1973 Rlog.d(TAG, "created new PersistAtoms"); 1974 return atoms; 1975 } 1976 1977 @VisibleForTesting 1978 protected long getWallTimeMillis() { 1979 // Epoch time in UTC, preserved across reboots, but can be adjusted e.g. by the user or NTP 1980 return System.currentTimeMillis(); 1981 } 1982 } 1983