• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.os.BackgroundThread;
33 import com.android.internal.telephony.flags.Flags;
34 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierIdMismatch;
35 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
36 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
37 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
38 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
39 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
40 import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation;
41 import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent;
42 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent;
43 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent;
44 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationFeatureTagStats;
45 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationServiceDescStats;
46 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats;
47 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
48 import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
49 import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequestsV2;
50 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
51 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
52 import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
53 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
54 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
55 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
56 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteAccessController;
57 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
58 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
59 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
60 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
61 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
62 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
63 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession;
64 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender;
65 import com.android.internal.telephony.nano.PersistAtomsProto.SipDelegateStats;
66 import com.android.internal.telephony.nano.PersistAtomsProto.SipMessageResponse;
67 import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportFeatureTagStats;
68 import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportSession;
69 import com.android.internal.telephony.nano.PersistAtomsProto.UceEventStats;
70 import com.android.internal.telephony.nano.PersistAtomsProto.UnmeteredNetworks;
71 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage;
72 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
73 import com.android.internal.util.ArrayUtils;
74 import com.android.telephony.Rlog;
75 
76 import java.io.FileOutputStream;
77 import java.io.IOException;
78 import java.nio.file.Files;
79 import java.nio.file.NoSuchFileException;
80 import java.security.SecureRandom;
81 import java.util.Arrays;
82 import java.util.Comparator;
83 import java.util.stream.IntStream;
84 
85 /**
86  * Stores and aggregates metrics that should not be pulled at arbitrary frequency.
87  *
88  * <p>NOTE: while this class checks timestamp against {@code minIntervalMillis}, it is {@link
89  * MetricsCollector}'s responsibility to ensure {@code minIntervalMillis} is set correctly.
90  */
91 public class PersistAtomsStorage {
92     private static final String TAG = PersistAtomsStorage.class.getSimpleName();
93 
94     /** Name of the file where cached statistics are saved to. */
95     private static final String FILENAME = "persist_atoms.pb";
96 
97     /** Delay to store atoms to persistent storage to bundle multiple operations together. */
98     private static final int SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS = 30000;
99 
100     /**
101      * Delay to store atoms to persistent storage during pulls to avoid unnecessary operations.
102      *
103      * <p>This delay should be short to avoid duplicating atoms or losing pull timestamp in case of
104      * crash or power loss.
105      */
106     private static final int SAVE_TO_FILE_DELAY_FOR_GET_MILLIS = 500;
107 
108     /** Maximum number of call sessions to store between pulls. */
109     private final int mMaxNumVoiceCallSessions;
110 
111     /**
112      * Maximum number of SMS to store between pulls. Incoming messages and outgoing messages are
113      * counted separately.
114      */
115     private final int mMaxNumSms;
116 
117     /**
118      * Maximum number of carrier ID mismatch events stored on the device to avoid sending duplicated
119      * metrics.
120      */
121     private final int mMaxNumCarrierIdMismatches;
122 
123     /** Maximum number of data call sessions to store during pulls. */
124     private final int mMaxNumDataCallSessions;
125 
126     /** Maximum number of service states to store between pulls. */
127     private final int mMaxNumCellularServiceStates;
128 
129     /** Maximum number of data service switches to store between pulls. */
130     private final int mMaxNumCellularDataSwitches;
131 
132     /** Maximum number of IMS registration stats to store between pulls. */
133     private final int mMaxNumImsRegistrationStats;
134 
135     /** Maximum number of IMS registration terminations to store between pulls. */
136     private final int mMaxNumImsRegistrationTerminations;
137 
138     /** Maximum number of IMS Registration Feature Tags to store between pulls. */
139     private final int mMaxNumImsRegistrationFeatureStats;
140 
141     /** Maximum number of RCS Client Provisioning to store between pulls. */
142     private final int mMaxNumRcsClientProvisioningStats;
143 
144     /** Maximum number of RCS Acs Provisioning to store between pulls. */
145     private final int mMaxNumRcsAcsProvisioningStats;
146 
147     /** Maximum number of Sip Message Response to store between pulls. */
148     private final int mMaxNumSipMessageResponseStats;
149 
150     /** Maximum number of Sip Transport Session to store between pulls. */
151     private final int mMaxNumSipTransportSessionStats;
152 
153     /** Maximum number of Sip Delegate to store between pulls. */
154     private final int mMaxNumSipDelegateStats;
155 
156     /** Maximum number of Sip Transport Feature Tag to store between pulls. */
157     private final int mMaxNumSipTransportFeatureTagStats;
158 
159     /** Maximum number of Dedicated Bearer Listener Event to store between pulls. */
160     private final int mMaxNumDedicatedBearerListenerEventStats;
161 
162     /** Maximum number of Dedicated Bearer Event to store between pulls. */
163     private final int mMaxNumDedicatedBearerEventStats;
164 
165     /** Maximum number of IMS Registration Service Desc to store between pulls. */
166     private final int mMaxNumImsRegistrationServiceDescStats;
167 
168     /** Maximum number of UCE Event to store between pulls. */
169     private final int mMaxNumUceEventStats;
170 
171     /** Maximum number of Presence Notify Event to store between pulls. */
172     private final int mMaxNumPresenceNotifyEventStats;
173 
174     /** Maximum number of GBA Event to store between pulls. */
175     private final int mMaxNumGbaEventStats;
176 
177     /** Maximum number of outgoing short code sms to store between pulls. */
178     private final int mMaxOutgoingShortCodeSms;
179 
180     /** Maximum number of Satellite relevant stats to store between pulls. */
181     private final int mMaxNumSatelliteStats;
182     private final int mMaxNumCarrierRoamingSatelliteSessionStats = 1;
183 
184     /** Maximum number of data network validation to store during pulls. */
185     private final int mMaxNumDataNetworkValidation;
186 
187     /** Stores persist atoms and persist states of the puller. */
188     @VisibleForTesting protected PersistAtoms mAtoms;
189 
190     /** Aggregates RAT duration and call count. */
191     private final VoiceCallRatTracker mVoiceCallRatTracker;
192 
193     /** Whether atoms should be saved immediately, skipping the delay. */
194     @VisibleForTesting protected boolean mSaveImmediately;
195 
196     private final Context mContext;
197     private final Handler mHandler;
198     private final HandlerThread mHandlerThread;
199     private static final SecureRandom sRandom = new SecureRandom();
200 
201     private Runnable mSaveRunnable =
202             new Runnable() {
203                 @Override
204                 public void run() {
205                     saveAtomsToFileNow();
206                 }
207             };
208 
PersistAtomsStorage(Context context)209     public PersistAtomsStorage(Context context) {
210         mContext = context;
211 
212         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_RAM_LOW)) {
213             Rlog.i(TAG, "Low RAM device");
214             mMaxNumVoiceCallSessions = 10;
215             mMaxNumSms = 5;
216             mMaxNumCarrierIdMismatches = 8;
217             mMaxNumDataCallSessions = 5;
218             mMaxNumCellularServiceStates = 10;
219             mMaxNumCellularDataSwitches = 5;
220             mMaxNumImsRegistrationStats = 5;
221             mMaxNumImsRegistrationTerminations = 5;
222             mMaxNumImsRegistrationFeatureStats = 15;
223             mMaxNumRcsClientProvisioningStats = 5;
224             mMaxNumRcsAcsProvisioningStats = 5;
225             mMaxNumSipMessageResponseStats = 10;
226             mMaxNumSipTransportSessionStats = 10;
227             mMaxNumSipDelegateStats = 5;
228             mMaxNumSipTransportFeatureTagStats = 15;
229             mMaxNumDedicatedBearerListenerEventStats = 5;
230             mMaxNumDedicatedBearerEventStats = 5;
231             mMaxNumImsRegistrationServiceDescStats = 15;
232             mMaxNumUceEventStats = 5;
233             mMaxNumPresenceNotifyEventStats = 10;
234             mMaxNumGbaEventStats = 5;
235             mMaxOutgoingShortCodeSms = 5;
236             mMaxNumSatelliteStats = 5;
237             mMaxNumDataNetworkValidation = 5;
238         } else {
239             mMaxNumVoiceCallSessions = 50;
240             mMaxNumSms = 25;
241             mMaxNumCarrierIdMismatches = 40;
242             mMaxNumDataCallSessions = 15;
243             mMaxNumCellularServiceStates = 50;
244             mMaxNumCellularDataSwitches = 50;
245             mMaxNumImsRegistrationStats = 10;
246             mMaxNumImsRegistrationTerminations = 10;
247             mMaxNumImsRegistrationFeatureStats = 25;
248             mMaxNumRcsClientProvisioningStats = 10;
249             mMaxNumRcsAcsProvisioningStats = 10;
250             mMaxNumSipMessageResponseStats = 25;
251             mMaxNumSipTransportSessionStats = 25;
252             mMaxNumSipDelegateStats = 10;
253             mMaxNumSipTransportFeatureTagStats = 25;
254             mMaxNumDedicatedBearerListenerEventStats = 10;
255             mMaxNumDedicatedBearerEventStats = 10;
256             mMaxNumImsRegistrationServiceDescStats = 25;
257             mMaxNumUceEventStats = 25;
258             mMaxNumPresenceNotifyEventStats = 50;
259             mMaxNumGbaEventStats = 10;
260             mMaxOutgoingShortCodeSms = 10;
261             mMaxNumSatelliteStats = 15;
262             mMaxNumDataNetworkValidation = 15;
263         }
264 
265         mAtoms = loadAtomsFromFile();
266         mVoiceCallRatTracker = VoiceCallRatTracker.fromProto(mAtoms.voiceCallRatUsage);
267 
268         if (Flags.threadShred()) {
269             mHandlerThread = null;
270             mHandler = new Handler(BackgroundThread.get().getLooper());
271         } else {
272             // TODO: we might be able to make mHandlerThread a local variable
273             mHandlerThread = new HandlerThread("PersistAtomsThread");
274             mHandlerThread.start();
275             mHandler = new Handler(mHandlerThread.getLooper());
276         }
277         mSaveImmediately = false;
278     }
279 
280     /** Adds a call to the storage. */
addVoiceCallSession(VoiceCallSession call)281     public synchronized void addVoiceCallSession(VoiceCallSession call) {
282         mAtoms.voiceCallSession =
283                 insertAtRandomPlace(mAtoms.voiceCallSession, call, mMaxNumVoiceCallSessions);
284         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
285 
286         Rlog.d(TAG, "Add new voice call session: " + call.toString());
287     }
288 
289     /** Adds RAT usages to the storage when a call session ends. */
addVoiceCallRatUsage(VoiceCallRatTracker ratUsages)290     public synchronized void addVoiceCallRatUsage(VoiceCallRatTracker ratUsages) {
291         mVoiceCallRatTracker.mergeWith(ratUsages);
292         mAtoms.voiceCallRatUsage = mVoiceCallRatTracker.toProto();
293         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
294     }
295 
296     /** Adds an incoming SMS to the storage. */
addIncomingSms(IncomingSms sms)297     public synchronized void addIncomingSms(IncomingSms sms) {
298         sms.hashCode = SmsStats.getSmsHashCode(sms);
299         mAtoms.incomingSms = insertAtRandomPlace(mAtoms.incomingSms, sms, mMaxNumSms);
300         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
301 
302         // To be removed
303         Rlog.d(TAG, "Add new incoming SMS atom: " + sms.toString());
304     }
305 
306     /** Adds an outgoing SMS to the storage. */
addOutgoingSms(OutgoingSms sms)307     public synchronized void addOutgoingSms(OutgoingSms sms) {
308         sms.hashCode = SmsStats.getSmsHashCode(sms);
309         // Update the retry id, if needed, so that it's unique and larger than all
310         // previous ones. (this algorithm ignores the fact that some SMS atoms might
311         // be dropped due to limit in size of the array).
312         for (OutgoingSms storedSms : mAtoms.outgoingSms) {
313             if (storedSms.messageId == sms.messageId && storedSms.retryId >= sms.retryId) {
314                 sms.retryId = storedSms.retryId + 1;
315             }
316         }
317 
318         mAtoms.outgoingSms = insertAtRandomPlace(mAtoms.outgoingSms, sms, mMaxNumSms);
319         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
320 
321         // To be removed
322         Rlog.d(TAG, "Add new outgoing SMS atom: " + sms.toString());
323     }
324 
325     /** Adds a service state to the storage, together with data service switch if any. */
addCellularServiceStateAndCellularDataServiceSwitch( CellularServiceState state, @Nullable CellularDataServiceSwitch serviceSwitch)326     public synchronized void addCellularServiceStateAndCellularDataServiceSwitch(
327             CellularServiceState state, @Nullable CellularDataServiceSwitch serviceSwitch) {
328         CellularServiceState existingState = find(state);
329         if (existingState != null) {
330             existingState.totalTimeMillis += state.totalTimeMillis;
331             existingState.lastUsedMillis = getWallTimeMillis();
332         } else {
333             state.lastUsedMillis = getWallTimeMillis();
334             mAtoms.cellularServiceState =
335                     insertAtRandomPlace(
336                             mAtoms.cellularServiceState, state, mMaxNumCellularServiceStates);
337         }
338 
339         if (serviceSwitch != null) {
340             CellularDataServiceSwitch existingSwitch = find(serviceSwitch);
341             if (existingSwitch != null) {
342                 existingSwitch.switchCount += serviceSwitch.switchCount;
343                 existingSwitch.lastUsedMillis = getWallTimeMillis();
344             } else {
345                 serviceSwitch.lastUsedMillis = getWallTimeMillis();
346                 mAtoms.cellularDataServiceSwitch =
347                         insertAtRandomPlace(
348                                 mAtoms.cellularDataServiceSwitch,
349                                 serviceSwitch,
350                                 mMaxNumCellularDataSwitches);
351             }
352         }
353 
354         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
355     }
356 
357     /** Adds a data call session to the storage. */
addDataCallSession(DataCallSession dataCall)358     public synchronized void addDataCallSession(DataCallSession dataCall) {
359         int index = findIndex(dataCall);
360         if (index >= 0) {
361             DataCallSession existingCall = mAtoms.dataCallSession[index];
362             dataCall.ratSwitchCount += existingCall.ratSwitchCount;
363             dataCall.durationMinutes += existingCall.durationMinutes;
364 
365             dataCall.handoverFailureCauses = IntStream.concat(Arrays.stream(
366                             dataCall.handoverFailureCauses),
367                     Arrays.stream(existingCall.handoverFailureCauses))
368                     .limit(DataCallSessionStats.SIZE_LIMIT_HANDOVER_FAILURES).toArray();
369             dataCall.handoverFailureRat = IntStream.concat(Arrays.stream(
370                             dataCall.handoverFailureRat),
371                     Arrays.stream(existingCall.handoverFailureRat))
372                     .limit(DataCallSessionStats.SIZE_LIMIT_HANDOVER_FAILURES).toArray();
373 
374             mAtoms.dataCallSession[index] = dataCall;
375         } else {
376             mAtoms.dataCallSession =
377                     insertAtRandomPlace(mAtoms.dataCallSession, dataCall, mMaxNumDataCallSessions);
378         }
379 
380         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
381     }
382 
383     /**
384      * Adds a new carrier ID mismatch event to the storage.
385      *
386      * @return true if the item was not present and was added to the persistent storage, false
387      *     otherwise.
388      */
addCarrierIdMismatch(CarrierIdMismatch carrierIdMismatch)389     public synchronized boolean addCarrierIdMismatch(CarrierIdMismatch carrierIdMismatch) {
390         // Check if the details of the SIM cards are already present and in case return.
391         if (find(carrierIdMismatch) != null) {
392             return false;
393         }
394         // Add the new CarrierIdMismatch at the end of the array, so that the same atom will not be
395         // sent again in future.
396         if (mAtoms.carrierIdMismatch.length == mMaxNumCarrierIdMismatches) {
397             System.arraycopy(
398                     mAtoms.carrierIdMismatch,
399                     1,
400                     mAtoms.carrierIdMismatch,
401                     0,
402                     mMaxNumCarrierIdMismatches - 1);
403             mAtoms.carrierIdMismatch[mMaxNumCarrierIdMismatches - 1] = carrierIdMismatch;
404         } else {
405             mAtoms.carrierIdMismatch =
406                     ArrayUtils.appendElement(
407                             CarrierIdMismatch.class,
408                             mAtoms.carrierIdMismatch,
409                             carrierIdMismatch,
410                             true);
411         }
412         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
413         return true;
414     }
415 
416     /** Adds IMS registration stats to the storage. */
addImsRegistrationStats(ImsRegistrationStats stats)417     public synchronized void addImsRegistrationStats(ImsRegistrationStats stats) {
418         ImsRegistrationStats existingStats = find(stats);
419         if (existingStats != null) {
420             existingStats.registeredMillis += stats.registeredMillis;
421             existingStats.voiceCapableMillis += stats.voiceCapableMillis;
422             existingStats.voiceAvailableMillis += stats.voiceAvailableMillis;
423             existingStats.smsCapableMillis += stats.smsCapableMillis;
424             existingStats.smsAvailableMillis += stats.smsAvailableMillis;
425             existingStats.videoCapableMillis += stats.videoCapableMillis;
426             existingStats.videoAvailableMillis += stats.videoAvailableMillis;
427             existingStats.utCapableMillis += stats.utCapableMillis;
428             existingStats.utAvailableMillis += stats.utAvailableMillis;
429             existingStats.registeringMillis += stats.registeringMillis;
430             existingStats.unregisteredMillis += stats.unregisteredMillis;
431             existingStats.registeredTimes += stats.registeredTimes;
432             existingStats.lastUsedMillis = getWallTimeMillis();
433         } else {
434             stats.lastUsedMillis = getWallTimeMillis();
435             mAtoms.imsRegistrationStats =
436                     insertAtRandomPlace(
437                             mAtoms.imsRegistrationStats, stats, mMaxNumImsRegistrationStats);
438         }
439         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
440     }
441 
442     /** Adds IMS registration termination to the storage. */
addImsRegistrationTermination(ImsRegistrationTermination termination)443     public synchronized void addImsRegistrationTermination(ImsRegistrationTermination termination) {
444         ImsRegistrationTermination existingTermination = find(termination);
445         if (existingTermination != null) {
446             existingTermination.count += termination.count;
447             existingTermination.lastUsedMillis = getWallTimeMillis();
448         } else {
449             termination.lastUsedMillis = getWallTimeMillis();
450             mAtoms.imsRegistrationTermination =
451                     insertAtRandomPlace(
452                             mAtoms.imsRegistrationTermination,
453                             termination,
454                             mMaxNumImsRegistrationTerminations);
455         }
456         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
457     }
458 
459     /**
460      * Stores the version of the carrier ID matching table.
461      *
462      * @return true if the version is newer than last available version, false otherwise.
463      */
setCarrierIdTableVersion(int carrierIdTableVersion)464     public synchronized boolean setCarrierIdTableVersion(int carrierIdTableVersion) {
465         if (mAtoms.carrierIdTableVersion < carrierIdTableVersion) {
466             mAtoms.carrierIdTableVersion = carrierIdTableVersion;
467             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
468             return true;
469         } else {
470             return false;
471         }
472     }
473 
474     /**
475      * Store the number of times auto data switch feature is toggled.
476      */
recordToggledAutoDataSwitch()477     public synchronized void recordToggledAutoDataSwitch() {
478         mAtoms.autoDataSwitchToggleCount++;
479         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
480     }
481 
482     /** Adds a new {@link NetworkRequestsV2} to the storage. */
addNetworkRequestsV2(NetworkRequestsV2 networkRequests)483     public synchronized void addNetworkRequestsV2(NetworkRequestsV2 networkRequests) {
484         NetworkRequestsV2 existingMetrics = find(networkRequests);
485         if (existingMetrics != null) {
486             existingMetrics.requestCount += networkRequests.requestCount;
487         } else {
488             NetworkRequestsV2 newMetrics = new NetworkRequestsV2();
489             newMetrics.capability = networkRequests.capability;
490             newMetrics.carrierId = networkRequests.carrierId;
491             newMetrics.requestCount = networkRequests.requestCount;
492             mAtoms.networkRequestsV2 =
493                     ArrayUtils.appendElement(
494                             NetworkRequestsV2.class, mAtoms.networkRequestsV2, newMetrics, true);
495         }
496         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
497     }
498 
499     /** Adds a new {@link ImsRegistrationFeatureTagStats} to the storage. */
addImsRegistrationFeatureTagStats( ImsRegistrationFeatureTagStats stats)500     public synchronized void addImsRegistrationFeatureTagStats(
501                 ImsRegistrationFeatureTagStats stats) {
502         ImsRegistrationFeatureTagStats existingStats = find(stats);
503         if (existingStats != null) {
504             existingStats.registeredMillis += stats.registeredMillis;
505         } else {
506             mAtoms.imsRegistrationFeatureTagStats =
507                 insertAtRandomPlace(mAtoms.imsRegistrationFeatureTagStats,
508                     stats, mMaxNumImsRegistrationFeatureStats);
509         }
510         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
511     }
512 
513     /** Adds a new {@link RcsClientProvisioningStats} to the storage. */
addRcsClientProvisioningStats(RcsClientProvisioningStats stats)514     public synchronized void addRcsClientProvisioningStats(RcsClientProvisioningStats stats) {
515         RcsClientProvisioningStats existingStats = find(stats);
516         if (existingStats != null) {
517             existingStats.count += 1;
518         } else {
519             mAtoms.rcsClientProvisioningStats =
520                 insertAtRandomPlace(mAtoms.rcsClientProvisioningStats, stats,
521                         mMaxNumRcsClientProvisioningStats);
522         }
523         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
524     }
525 
526     /** Adds a new {@link RcsAcsProvisioningStats} to the storage. */
addRcsAcsProvisioningStats(RcsAcsProvisioningStats stats)527     public synchronized void addRcsAcsProvisioningStats(RcsAcsProvisioningStats stats) {
528         RcsAcsProvisioningStats existingStats = find(stats);
529         if (existingStats != null) {
530             existingStats.count += 1;
531             existingStats.stateTimerMillis += stats.stateTimerMillis;
532         } else {
533             // prevent that wrong count from caller effects total count
534             stats.count = 1;
535             mAtoms.rcsAcsProvisioningStats =
536                 insertAtRandomPlace(mAtoms.rcsAcsProvisioningStats, stats,
537                         mMaxNumRcsAcsProvisioningStats);
538         }
539         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
540     }
541 
542     /** Adds a new {@link SipDelegateStats} to the storage. */
addSipDelegateStats(SipDelegateStats stats)543     public synchronized void addSipDelegateStats(SipDelegateStats stats) {
544         mAtoms.sipDelegateStats = insertAtRandomPlace(mAtoms.sipDelegateStats, stats,
545                 mMaxNumSipDelegateStats);
546         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
547     }
548 
549     /** Adds a new {@link SipTransportFeatureTagStats} to the storage. */
addSipTransportFeatureTagStats(SipTransportFeatureTagStats stats)550     public synchronized void addSipTransportFeatureTagStats(SipTransportFeatureTagStats stats) {
551         SipTransportFeatureTagStats lastStat = find(stats);
552         if (lastStat != null) {
553             lastStat.associatedMillis += stats.associatedMillis;
554         } else {
555             mAtoms.sipTransportFeatureTagStats =
556                     insertAtRandomPlace(mAtoms.sipTransportFeatureTagStats, stats,
557                             mMaxNumSipTransportFeatureTagStats);
558         }
559         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
560     }
561 
562     /** Adds a new {@link SipMessageResponse} to the storage. */
addSipMessageResponse(SipMessageResponse stats)563     public synchronized void addSipMessageResponse(SipMessageResponse stats) {
564         SipMessageResponse existingStats = find(stats);
565         if (existingStats != null) {
566             existingStats.count += 1;
567         } else {
568             mAtoms.sipMessageResponse = insertAtRandomPlace(mAtoms.sipMessageResponse, stats,
569                     mMaxNumSipMessageResponseStats);
570         }
571         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
572     }
573 
574     /** Adds a new {@link SipTransportSession} to the storage. */
addCompleteSipTransportSession(SipTransportSession stats)575     public synchronized void addCompleteSipTransportSession(SipTransportSession stats) {
576         SipTransportSession existingStats = find(stats);
577         if (existingStats != null) {
578             existingStats.sessionCount += 1;
579             if (stats.isEndedGracefully) {
580                 existingStats.endedGracefullyCount += 1;
581             }
582         } else {
583             mAtoms.sipTransportSession =
584                     insertAtRandomPlace(mAtoms.sipTransportSession, stats,
585                             mMaxNumSipTransportSessionStats);
586         }
587         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
588     }
589 
590     /** Adds a new {@link ImsDedicatedBearerListenerEvent} to the storage. */
addImsDedicatedBearerListenerEvent( ImsDedicatedBearerListenerEvent stats)591     public synchronized void addImsDedicatedBearerListenerEvent(
592                 ImsDedicatedBearerListenerEvent stats) {
593         ImsDedicatedBearerListenerEvent existingStats = find(stats);
594         if (existingStats != null) {
595             existingStats.eventCount += 1;
596         } else {
597             mAtoms.imsDedicatedBearerListenerEvent =
598                 insertAtRandomPlace(mAtoms.imsDedicatedBearerListenerEvent,
599                     stats, mMaxNumDedicatedBearerListenerEventStats);
600         }
601         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
602     }
603 
604     /** Adds a new {@link ImsDedicatedBearerEvent} to the storage. */
addImsDedicatedBearerEvent(ImsDedicatedBearerEvent stats)605     public synchronized void addImsDedicatedBearerEvent(ImsDedicatedBearerEvent stats) {
606         ImsDedicatedBearerEvent existingStats = find(stats);
607         if (existingStats != null) {
608             existingStats.count += 1;
609         } else {
610             mAtoms.imsDedicatedBearerEvent =
611                 insertAtRandomPlace(mAtoms.imsDedicatedBearerEvent, stats,
612                         mMaxNumDedicatedBearerEventStats);
613         }
614         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
615     }
616 
617     /** Adds a new {@link ImsRegistrationServiceDescStats} to the storage. */
addImsRegistrationServiceDescStats( ImsRegistrationServiceDescStats stats)618     public synchronized void addImsRegistrationServiceDescStats(
619             ImsRegistrationServiceDescStats stats) {
620         ImsRegistrationServiceDescStats existingStats = find(stats);
621         if (existingStats != null) {
622             existingStats.publishedMillis += stats.publishedMillis;
623         } else {
624             mAtoms.imsRegistrationServiceDescStats =
625                 insertAtRandomPlace(mAtoms.imsRegistrationServiceDescStats,
626                     stats, mMaxNumImsRegistrationServiceDescStats);
627         }
628         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
629     }
630 
631     /** Adds a new {@link UceEventStats} to the storage. */
addUceEventStats(UceEventStats stats)632     public synchronized void addUceEventStats(UceEventStats stats) {
633         UceEventStats existingStats = find(stats);
634         if (existingStats != null) {
635             existingStats.count += 1;
636         } else {
637             mAtoms.uceEventStats =
638                 insertAtRandomPlace(mAtoms.uceEventStats, stats, mMaxNumUceEventStats);
639         }
640         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
641     }
642 
643     /** Adds a new {@link PresenceNotifyEvent} to the storage. */
addPresenceNotifyEvent(PresenceNotifyEvent stats)644     public synchronized void addPresenceNotifyEvent(PresenceNotifyEvent stats) {
645         PresenceNotifyEvent existingStats = find(stats);
646         if (existingStats != null) {
647             existingStats.rcsCapsCount += stats.rcsCapsCount;
648             existingStats.mmtelCapsCount += stats.mmtelCapsCount;
649             existingStats.noCapsCount += stats.noCapsCount;
650             existingStats.count += stats.count;
651         } else {
652             mAtoms.presenceNotifyEvent =
653                 insertAtRandomPlace(mAtoms.presenceNotifyEvent, stats,
654                         mMaxNumPresenceNotifyEventStats);
655         }
656         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
657     }
658 
659     /** Adds a new {@link GbaEvent} to the storage. */
addGbaEvent(GbaEvent stats)660     public synchronized void addGbaEvent(GbaEvent stats) {
661         GbaEvent existingStats = find(stats);
662         if (existingStats != null) {
663             existingStats.count += 1;
664         } else {
665             mAtoms.gbaEvent =
666                 insertAtRandomPlace(mAtoms.gbaEvent, stats, mMaxNumGbaEventStats);
667         }
668         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
669     }
670 
671     /**
672      *  Sets the unmetered networks bitmask for a given phone id. If the carrier id
673      *  doesn't match the existing UnmeteredNetworks' carrier id, the bitmask is
674      *  first reset to 0.
675      */
addUnmeteredNetworks( int phoneId, int carrierId, @NetworkTypeBitMask long bitmask)676     public synchronized void addUnmeteredNetworks(
677             int phoneId, int carrierId, @NetworkTypeBitMask long bitmask) {
678         UnmeteredNetworks stats = findUnmeteredNetworks(phoneId);
679         boolean needToSave = true;
680         if (stats == null) {
681             stats = new UnmeteredNetworks();
682             stats.phoneId = phoneId;
683             stats.carrierId = carrierId;
684             stats.unmeteredNetworksBitmask = bitmask;
685             mAtoms.unmeteredNetworks =
686                     ArrayUtils.appendElement(
687                             UnmeteredNetworks.class, mAtoms.unmeteredNetworks, stats, true);
688         } else {
689             // Reset the bitmask to 0 if carrier id doesn't match.
690             if (stats.carrierId != carrierId) {
691                 stats.carrierId = carrierId;
692                 stats.unmeteredNetworksBitmask = 0;
693             }
694             if ((stats.unmeteredNetworksBitmask | bitmask) != stats.unmeteredNetworksBitmask) {
695                 stats.unmeteredNetworksBitmask |= bitmask;
696             } else {
697                 needToSave = false;
698             }
699         }
700         // Only save if something changes.
701         if (needToSave) {
702             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
703         }
704     }
705 
706     /** Adds an outgoing short code sms to the storage. */
addOutgoingShortCodeSms(OutgoingShortCodeSms shortCodeSms)707     public synchronized void addOutgoingShortCodeSms(OutgoingShortCodeSms shortCodeSms) {
708         OutgoingShortCodeSms existingOutgoingShortCodeSms = find(shortCodeSms);
709         if (existingOutgoingShortCodeSms != null) {
710             existingOutgoingShortCodeSms.shortCodeSmsCount += 1;
711         } else {
712             mAtoms.outgoingShortCodeSms = insertAtRandomPlace(mAtoms.outgoingShortCodeSms,
713                     shortCodeSms, mMaxOutgoingShortCodeSms);
714         }
715         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
716     }
717 
718     /** Adds a new {@link SatelliteController} to the storage. */
addSatelliteControllerStats(SatelliteController stats)719     public synchronized void addSatelliteControllerStats(SatelliteController stats) {
720         // find existing satellite controller atom with same carrier ID.
721         SatelliteController existingStats = find(stats);
722         if (existingStats != null) {
723             existingStats.countOfSatelliteServiceEnablementsSuccess
724                     += stats.countOfSatelliteServiceEnablementsSuccess;
725             existingStats.countOfSatelliteServiceEnablementsFail
726                     += stats.countOfSatelliteServiceEnablementsFail;
727             existingStats.countOfOutgoingDatagramSuccess
728                     += stats.countOfOutgoingDatagramSuccess;
729             existingStats.countOfOutgoingDatagramFail
730                     += stats.countOfOutgoingDatagramFail;
731             existingStats.countOfIncomingDatagramSuccess
732                     += stats.countOfIncomingDatagramSuccess;
733             existingStats.countOfIncomingDatagramFail
734                     += stats.countOfIncomingDatagramFail;
735             existingStats.countOfDatagramTypeSosSmsSuccess
736                     += stats.countOfDatagramTypeSosSmsSuccess;
737             existingStats.countOfDatagramTypeSosSmsFail
738                     += stats.countOfDatagramTypeSosSmsFail;
739             existingStats.countOfDatagramTypeLocationSharingSuccess
740                     += stats.countOfDatagramTypeLocationSharingSuccess;
741             existingStats.countOfDatagramTypeLocationSharingFail
742                     += stats.countOfDatagramTypeLocationSharingFail;
743             existingStats.countOfProvisionSuccess
744                     += stats.countOfProvisionSuccess;
745             existingStats.countOfProvisionFail
746                     += stats.countOfProvisionFail;
747             existingStats.countOfDeprovisionSuccess
748                     += stats.countOfDeprovisionSuccess;
749             existingStats.countOfDeprovisionFail
750                     += stats.countOfDeprovisionFail;
751             existingStats.totalServiceUptimeSec
752                     += stats.totalServiceUptimeSec;
753             existingStats.totalBatteryConsumptionPercent
754                     += stats.totalBatteryConsumptionPercent;
755             existingStats.totalBatteryChargedTimeSec
756                     += stats.totalBatteryChargedTimeSec;
757             existingStats.countOfDemoModeSatelliteServiceEnablementsSuccess
758                     += stats.countOfDemoModeSatelliteServiceEnablementsSuccess;
759             existingStats.countOfDemoModeSatelliteServiceEnablementsFail
760                     += stats.countOfDemoModeSatelliteServiceEnablementsFail;
761             existingStats.countOfDemoModeOutgoingDatagramSuccess
762                     += stats.countOfDemoModeOutgoingDatagramSuccess;
763             existingStats.countOfDemoModeOutgoingDatagramFail
764                     += stats.countOfDemoModeOutgoingDatagramFail;
765             existingStats.countOfDemoModeIncomingDatagramSuccess
766                     += stats.countOfDemoModeIncomingDatagramSuccess;
767             existingStats.countOfDemoModeIncomingDatagramFail
768                     += stats.countOfDemoModeIncomingDatagramFail;
769             existingStats.countOfDatagramTypeKeepAliveSuccess
770                     += stats.countOfDatagramTypeKeepAliveSuccess;
771             existingStats.countOfDatagramTypeKeepAliveFail
772                     += stats.countOfDatagramTypeKeepAliveFail;
773             existingStats.countOfAllowedSatelliteAccess += stats.countOfAllowedSatelliteAccess;
774             existingStats.countOfDisallowedSatelliteAccess
775                     += stats.countOfDisallowedSatelliteAccess;
776             existingStats.countOfSatelliteAccessCheckFail += stats.countOfSatelliteAccessCheckFail;
777             // Does not update isProvisioned and carrierId due to they are dimension fields.
778             existingStats.countOfSatelliteAllowedStateChangedEvents
779                     += stats.countOfSatelliteAllowedStateChangedEvents;
780             existingStats.countOfSuccessfulLocationQueries +=
781                     stats.countOfSuccessfulLocationQueries;
782             existingStats.countOfFailedLocationQueries += stats.countOfFailedLocationQueries;
783             existingStats.countOfP2PSmsAvailableNotificationShown
784                     += stats.countOfP2PSmsAvailableNotificationShown;
785             existingStats.countOfP2PSmsAvailableNotificationRemoved
786                     += stats.countOfP2PSmsAvailableNotificationRemoved;
787             // Does not update isNtnOnlyCarrier due to it is a dimension field.
788             existingStats.versionOfSatelliteAccessConfig = stats.versionOfSatelliteAccessConfig;
789             existingStats.countOfIncomingDatagramTypeSosSmsSuccess
790                     += stats.countOfIncomingDatagramTypeSosSmsSuccess;
791             existingStats.countOfIncomingDatagramTypeSosSmsFail
792                     += stats.countOfIncomingDatagramTypeSosSmsFail;
793             existingStats.countOfOutgoingDatagramTypeSmsSuccess
794                     += stats.countOfOutgoingDatagramTypeSmsSuccess;
795             existingStats.countOfOutgoingDatagramTypeSmsFail
796                     += stats.countOfOutgoingDatagramTypeSmsFail;
797             existingStats.countOfIncomingDatagramTypeSmsSuccess
798                     += stats.countOfIncomingDatagramTypeSmsSuccess;
799             existingStats.countOfIncomingDatagramTypeSmsFail
800                     += stats.countOfIncomingDatagramTypeSmsFail;
801         } else {
802             mAtoms.satelliteController = insertAtRandomPlace(mAtoms.satelliteController, stats,
803                     mMaxNumSatelliteStats);
804         }
805         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
806     }
807 
808     /** Adds a new {@link SatelliteSession} to the storage. */
addSatelliteSessionStats(SatelliteSession stats)809     public synchronized void addSatelliteSessionStats(SatelliteSession stats) {
810         SatelliteSession existingStats = find(stats);
811         if (existingStats != null) {
812             existingStats.count += 1;
813         } else {
814             mAtoms.satelliteSession =
815                     insertAtRandomPlace(mAtoms.satelliteSession, stats, mMaxNumSatelliteStats);
816         }
817         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
818     }
819 
820     /** Adds a new {@link SatelliteIncomingDatagram} to the storage. */
addSatelliteIncomingDatagramStats(SatelliteIncomingDatagram stats)821     public synchronized void addSatelliteIncomingDatagramStats(SatelliteIncomingDatagram stats) {
822         mAtoms.satelliteIncomingDatagram =
823                 insertAtRandomPlace(mAtoms.satelliteIncomingDatagram, stats, mMaxNumSatelliteStats);
824         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
825     }
826 
827     /** Adds a new {@link SatelliteOutgoingDatagram} to the storage. */
addSatelliteOutgoingDatagramStats(SatelliteOutgoingDatagram stats)828     public synchronized void addSatelliteOutgoingDatagramStats(SatelliteOutgoingDatagram stats) {
829         mAtoms.satelliteOutgoingDatagram =
830                 insertAtRandomPlace(mAtoms.satelliteOutgoingDatagram, stats, mMaxNumSatelliteStats);
831         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
832     }
833 
834     /** Adds a new {@link SatelliteProvision} to the storage. */
addSatelliteProvisionStats(SatelliteProvision stats)835     public synchronized void addSatelliteProvisionStats(SatelliteProvision stats) {
836         mAtoms.satelliteProvision =
837                 insertAtRandomPlace(mAtoms.satelliteProvision, stats, mMaxNumSatelliteStats);
838         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
839     }
840 
841     /** Adds a new {@link SatelliteSosMessageRecommender} to the storage. */
addSatelliteSosMessageRecommenderStats( SatelliteSosMessageRecommender stats)842     public synchronized void addSatelliteSosMessageRecommenderStats(
843             SatelliteSosMessageRecommender stats) {
844         SatelliteSosMessageRecommender existingStats = find(stats);
845         if (existingStats != null) {
846             existingStats.count += 1;
847         } else {
848             mAtoms.satelliteSosMessageRecommender =
849                     insertAtRandomPlace(mAtoms.satelliteSosMessageRecommender, stats,
850                             mMaxNumSatelliteStats);
851         }
852         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
853     }
854 
855     /** Adds a data network validation to the storage. */
addDataNetworkValidation(DataNetworkValidation dataNetworkValidation)856     public synchronized void addDataNetworkValidation(DataNetworkValidation dataNetworkValidation) {
857         DataNetworkValidation existingStats = find(dataNetworkValidation);
858         if (existingStats != null) {
859             int count = existingStats.networkValidationCount
860                     + dataNetworkValidation.networkValidationCount;
861             long elapsedTime = ((dataNetworkValidation.elapsedTimeInMillis
862                     * dataNetworkValidation.networkValidationCount) + (
863                     existingStats.elapsedTimeInMillis * existingStats.networkValidationCount))
864                     / count;
865             existingStats.networkValidationCount = count;
866             existingStats.elapsedTimeInMillis = elapsedTime;
867         } else {
868             mAtoms.dataNetworkValidation = insertAtRandomPlace(
869                     mAtoms.dataNetworkValidation, dataNetworkValidation, mMaxNumDataCallSessions);
870         }
871         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
872     }
873 
874     /** Adds a new {@link CarrierRoamingSatelliteSession} to the storage. */
addCarrierRoamingSatelliteSessionStats( CarrierRoamingSatelliteSession stats)875     public synchronized void addCarrierRoamingSatelliteSessionStats(
876             CarrierRoamingSatelliteSession stats) {
877         mAtoms.carrierRoamingSatelliteSession = insertAtRandomPlace(
878                 mAtoms.carrierRoamingSatelliteSession, stats,
879                 mMaxNumCarrierRoamingSatelliteSessionStats);
880         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
881     }
882 
883     /** Adds a new {@link CarrierRoamingSatelliteControllerStats} to the storage. */
addCarrierRoamingSatelliteControllerStats( CarrierRoamingSatelliteControllerStats stats)884     public synchronized void addCarrierRoamingSatelliteControllerStats(
885             CarrierRoamingSatelliteControllerStats stats) {
886         CarrierRoamingSatelliteControllerStats existingStats = find(stats);
887         if (existingStats != null) {
888             existingStats.countOfEntitlementStatusQueryRequest +=
889                     stats.countOfEntitlementStatusQueryRequest;
890             existingStats.countOfSatelliteConfigUpdateRequest +=
891                     stats.countOfSatelliteConfigUpdateRequest;
892             existingStats.countOfSatelliteNotificationDisplayed +=
893                     stats.countOfSatelliteNotificationDisplayed;
894             existingStats.satelliteSessionGapMinSec = stats.satelliteSessionGapMinSec;
895             existingStats.satelliteSessionGapAvgSec = stats.satelliteSessionGapAvgSec;
896             existingStats.satelliteSessionGapMaxSec = stats.satelliteSessionGapMaxSec;
897             // Does not update configDataSource, carrierId, isDeviceEntitled, due to  they are
898             // dimension fields.
899             existingStats.isDeviceEntitled = stats.isDeviceEntitled;
900             existingStats.isMultiSim = stats.isMultiSim;
901             existingStats.countOfSatelliteSessions += stats.countOfSatelliteSessions;
902             existingStats.isNbIotNtn = stats.isNbIotNtn;
903         } else {
904             mAtoms.carrierRoamingSatelliteControllerStats = insertAtRandomPlace(
905                     mAtoms.carrierRoamingSatelliteControllerStats, stats, mMaxNumSatelliteStats);
906         }
907 
908         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
909     }
910 
911     /** Adds a new {@link SatelliteEntitlement} to the storage. */
addSatelliteEntitlementStats(SatelliteEntitlement stats)912     public synchronized void addSatelliteEntitlementStats(SatelliteEntitlement stats) {
913         SatelliteEntitlement existingStats = find(stats);
914         if (existingStats != null) {
915             existingStats.count += 1;
916         } else {
917             mAtoms.satelliteEntitlement = insertAtRandomPlace(mAtoms.satelliteEntitlement,
918                     stats, mMaxNumSatelliteStats);
919         }
920         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
921     }
922 
923     /** Adds a new {@link SatelliteConfigUpdater} to the storage. */
addSatelliteConfigUpdaterStats(SatelliteConfigUpdater stats)924     public synchronized void addSatelliteConfigUpdaterStats(SatelliteConfigUpdater stats) {
925         SatelliteConfigUpdater existingStats = find(stats);
926         if (existingStats != null) {
927             existingStats.count += 1;
928         } else {
929             mAtoms.satelliteConfigUpdater = insertAtRandomPlace(mAtoms.satelliteConfigUpdater,
930                     stats, mMaxNumSatelliteStats);
931         }
932         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
933     }
934 
935     /** Adds a new {@link SatelliteAccessController} to the storage. */
addSatelliteAccessControllerStats(SatelliteAccessController stats)936     public synchronized void addSatelliteAccessControllerStats(SatelliteAccessController stats) {
937         mAtoms.satelliteAccessController =
938                 insertAtRandomPlace(mAtoms.satelliteAccessController, stats,
939                         mMaxNumSatelliteStats);
940         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
941     }
942 
943     /**
944      * Returns and clears the voice call sessions if last pulled longer than {@code
945      * minIntervalMillis} ago, otherwise returns {@code null}.
946      */
947     @Nullable
getVoiceCallSessions(long minIntervalMillis)948     public synchronized VoiceCallSession[] getVoiceCallSessions(long minIntervalMillis) {
949         if (getWallTimeMillis() - mAtoms.voiceCallSessionPullTimestampMillis > minIntervalMillis) {
950             mAtoms.voiceCallSessionPullTimestampMillis = getWallTimeMillis();
951             VoiceCallSession[] previousCalls = mAtoms.voiceCallSession;
952             mAtoms.voiceCallSession = new VoiceCallSession[0];
953             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
954             return previousCalls;
955         } else {
956             return null;
957         }
958     }
959 
960     /**
961      * Returns and clears the voice call RAT usages if last pulled longer than {@code
962      * minIntervalMillis} ago, otherwise returns {@code null}.
963      */
964     @Nullable
getVoiceCallRatUsages(long minIntervalMillis)965     public synchronized VoiceCallRatUsage[] getVoiceCallRatUsages(long minIntervalMillis) {
966         if (getWallTimeMillis() - mAtoms.voiceCallRatUsagePullTimestampMillis > minIntervalMillis) {
967             mAtoms.voiceCallRatUsagePullTimestampMillis = getWallTimeMillis();
968             VoiceCallRatUsage[] previousUsages = mAtoms.voiceCallRatUsage;
969             mVoiceCallRatTracker.clear();
970             mAtoms.voiceCallRatUsage = new VoiceCallRatUsage[0];
971             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
972             return previousUsages;
973         } else {
974             return null;
975         }
976     }
977 
978     /**
979      * Returns and clears the incoming SMS if last pulled longer than {@code minIntervalMillis} ago,
980      * otherwise returns {@code null}.
981      */
982     @Nullable
getIncomingSms(long minIntervalMillis)983     public synchronized IncomingSms[] getIncomingSms(long minIntervalMillis) {
984         if (getWallTimeMillis() - mAtoms.incomingSmsPullTimestampMillis > minIntervalMillis) {
985             mAtoms.incomingSmsPullTimestampMillis = getWallTimeMillis();
986             IncomingSms[] previousIncomingSms = mAtoms.incomingSms;
987             mAtoms.incomingSms = new IncomingSms[0];
988             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
989             return previousIncomingSms;
990         } else {
991             return null;
992         }
993     }
994 
995     /**
996      * Returns and clears the outgoing SMS if last pulled longer than {@code minIntervalMillis} ago,
997      * otherwise returns {@code null}.
998      */
999     @Nullable
getOutgoingSms(long minIntervalMillis)1000     public synchronized OutgoingSms[] getOutgoingSms(long minIntervalMillis) {
1001         if (getWallTimeMillis() - mAtoms.outgoingSmsPullTimestampMillis > minIntervalMillis) {
1002             mAtoms.outgoingSmsPullTimestampMillis = getWallTimeMillis();
1003             OutgoingSms[] previousOutgoingSms = mAtoms.outgoingSms;
1004             mAtoms.outgoingSms = new OutgoingSms[0];
1005             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1006             return previousOutgoingSms;
1007         } else {
1008             return null;
1009         }
1010     }
1011 
1012     /**
1013      * Returns and clears the data call session if last pulled longer than {@code minIntervalMillis}
1014      * ago, otherwise returns {@code null}.
1015      */
1016     @Nullable
getDataCallSessions(long minIntervalMillis)1017     public synchronized DataCallSession[] getDataCallSessions(long minIntervalMillis) {
1018         if (getWallTimeMillis() - mAtoms.dataCallSessionPullTimestampMillis > minIntervalMillis) {
1019             mAtoms.dataCallSessionPullTimestampMillis = getWallTimeMillis();
1020             DataCallSession[] previousDataCallSession = mAtoms.dataCallSession;
1021             mAtoms.dataCallSession = new DataCallSession[0];
1022             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1023             for (DataCallSession dataCallSession : previousDataCallSession) {
1024                 // sort to de-correlate any potential pattern for UII concern
1025                 sortBaseOnArray(dataCallSession.handoverFailureCauses,
1026                         dataCallSession.handoverFailureRat);
1027             }
1028             return previousDataCallSession;
1029         } else {
1030             return null;
1031         }
1032     }
1033 
1034     /**
1035      * Sort the other array base on the natural order of the primary array. Both arrays will be
1036      * sorted in-place.
1037      * @param primary The primary array to be sorted.
1038      * @param other The other array to be sorted in the order of primary array.
1039      */
sortBaseOnArray(int[] primary, int[] other)1040     private void sortBaseOnArray(int[] primary, int[] other) {
1041         if (other.length != primary.length) return;
1042         int[] index = IntStream.range(0, primary.length).boxed()
1043                 .sorted(Comparator.comparingInt(i -> primary[i]))
1044                 .mapToInt(Integer::intValue)
1045                 .toArray();
1046         int[] primaryCopy = Arrays.copyOf(primary,  primary.length);
1047         int[] otherCopy = Arrays.copyOf(other,  other.length);
1048         for (int i = 0; i < index.length; i++) {
1049             primary[i] = primaryCopy[index[i]];
1050             other[i] = otherCopy[index[i]];
1051         }
1052     }
1053 
1054 
1055     /**
1056      * Returns and clears the service state durations if last pulled longer than {@code
1057      * minIntervalMillis} ago, otherwise returns {@code null}.
1058      */
1059     @Nullable
getCellularServiceStates(long minIntervalMillis)1060     public synchronized CellularServiceState[] getCellularServiceStates(long minIntervalMillis) {
1061         if (getWallTimeMillis() - mAtoms.cellularServiceStatePullTimestampMillis
1062                 > minIntervalMillis) {
1063             mAtoms.cellularServiceStatePullTimestampMillis = getWallTimeMillis();
1064             CellularServiceState[] previousStates = mAtoms.cellularServiceState;
1065             Arrays.stream(previousStates).forEach(state -> state.lastUsedMillis = 0L);
1066             mAtoms.cellularServiceState = new CellularServiceState[0];
1067             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1068             return previousStates;
1069         } else {
1070             return null;
1071         }
1072     }
1073 
1074     /**
1075      * Returns and clears the service state durations if last pulled longer than {@code
1076      * minIntervalMillis} ago, otherwise returns {@code null}.
1077      */
1078     @Nullable
getCellularDataServiceSwitches( long minIntervalMillis)1079     public synchronized CellularDataServiceSwitch[] getCellularDataServiceSwitches(
1080             long minIntervalMillis) {
1081         if (getWallTimeMillis() - mAtoms.cellularDataServiceSwitchPullTimestampMillis
1082                 > minIntervalMillis) {
1083             mAtoms.cellularDataServiceSwitchPullTimestampMillis = getWallTimeMillis();
1084             CellularDataServiceSwitch[] previousSwitches = mAtoms.cellularDataServiceSwitch;
1085             Arrays.stream(previousSwitches)
1086                     .forEach(serviceSwitch -> serviceSwitch.lastUsedMillis = 0L);
1087             mAtoms.cellularDataServiceSwitch = new CellularDataServiceSwitch[0];
1088             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1089             return previousSwitches;
1090         } else {
1091             return null;
1092         }
1093     }
1094 
1095     /**
1096      * Returns and clears the IMS registration statistics normalized to 24h cycle if last
1097      * pulled longer than {@code minIntervalMillis} ago, otherwise returns {@code null}.
1098      */
1099     @Nullable
getImsRegistrationStats(long minIntervalMillis)1100     public synchronized ImsRegistrationStats[] getImsRegistrationStats(long minIntervalMillis) {
1101         long intervalMillis =
1102                 getWallTimeMillis() - mAtoms.imsRegistrationStatsPullTimestampMillis;
1103         if (intervalMillis > minIntervalMillis) {
1104             mAtoms.imsRegistrationStatsPullTimestampMillis = getWallTimeMillis();
1105             ImsRegistrationStats[] previousStats = mAtoms.imsRegistrationStats;
1106             Arrays.stream(previousStats).forEach(stats -> stats.lastUsedMillis = 0L);
1107             mAtoms.imsRegistrationStats = new ImsRegistrationStats[0];
1108             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1109             return normalizeData(previousStats, intervalMillis);
1110         } else {
1111             return null;
1112         }
1113     }
1114 
1115     /**
1116      * Returns and clears the IMS registration terminations if last pulled longer than {@code
1117      * minIntervalMillis} ago, otherwise returns {@code null}.
1118      */
1119     @Nullable
getImsRegistrationTerminations( long minIntervalMillis)1120     public synchronized ImsRegistrationTermination[] getImsRegistrationTerminations(
1121             long minIntervalMillis) {
1122         if (getWallTimeMillis() - mAtoms.imsRegistrationTerminationPullTimestampMillis
1123                 > minIntervalMillis) {
1124             mAtoms.imsRegistrationTerminationPullTimestampMillis = getWallTimeMillis();
1125             ImsRegistrationTermination[] previousTerminations = mAtoms.imsRegistrationTermination;
1126             Arrays.stream(previousTerminations)
1127                     .forEach(termination -> termination.lastUsedMillis = 0L);
1128             mAtoms.imsRegistrationTermination = new ImsRegistrationTermination[0];
1129             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1130             return previousTerminations;
1131         } else {
1132             return null;
1133         }
1134     }
1135 
1136     /**
1137      * Returns and clears the network requests if last pulled longer than {@code
1138      * minIntervalMillis} ago, otherwise returns {@code null}.
1139      */
1140     @Nullable
getNetworkRequestsV2(long minIntervalMillis)1141     public synchronized NetworkRequestsV2[] getNetworkRequestsV2(long minIntervalMillis) {
1142         if (getWallTimeMillis() - mAtoms.networkRequestsV2PullTimestampMillis > minIntervalMillis) {
1143             mAtoms.networkRequestsV2PullTimestampMillis = getWallTimeMillis();
1144             NetworkRequestsV2[] previousNetworkRequests = mAtoms.networkRequestsV2;
1145             mAtoms.networkRequestsV2 = new NetworkRequestsV2[0];
1146             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1147             return previousNetworkRequests;
1148         } else {
1149             return null;
1150         }
1151     }
1152 
1153     /** @return the number of times auto data switch mobile data policy is toggled. */
getAutoDataSwitchToggleCount()1154     public synchronized int getAutoDataSwitchToggleCount() {
1155         int count = mAtoms.autoDataSwitchToggleCount;
1156         if (count > 0) {
1157             mAtoms.autoDataSwitchToggleCount = 0;
1158             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1159         }
1160         return count;
1161     }
1162 
1163     /**
1164      * Returns and clears the ImsRegistrationFeatureTagStats if last pulled longer than
1165      * {@code minIntervalMillis} ago, otherwise returns {@code null}.
1166      */
1167     @Nullable
getImsRegistrationFeatureTagStats( long minIntervalMillis)1168     public synchronized ImsRegistrationFeatureTagStats[] getImsRegistrationFeatureTagStats(
1169             long minIntervalMillis) {
1170         long intervalMillis =
1171                 getWallTimeMillis() - mAtoms.rcsAcsProvisioningStatsPullTimestampMillis;
1172         if (intervalMillis > minIntervalMillis) {
1173             mAtoms.imsRegistrationFeatureTagStatsPullTimestampMillis = getWallTimeMillis();
1174             ImsRegistrationFeatureTagStats[] previousStats =
1175                     mAtoms.imsRegistrationFeatureTagStats;
1176             mAtoms.imsRegistrationFeatureTagStats = new ImsRegistrationFeatureTagStats[0];
1177             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1178             return previousStats;
1179         } else {
1180             return null;
1181         }
1182     }
1183 
1184     /**
1185      * Returns and clears the RcsClientProvisioningStats if last pulled longer than {@code
1186      * minIntervalMillis} ago, otherwise returns {@code null}.
1187      */
1188     @Nullable
getRcsClientProvisioningStats( long minIntervalMillis)1189     public synchronized RcsClientProvisioningStats[] getRcsClientProvisioningStats(
1190             long minIntervalMillis) {
1191         if (getWallTimeMillis() - mAtoms.rcsClientProvisioningStatsPullTimestampMillis
1192                 > minIntervalMillis) {
1193             mAtoms.rcsClientProvisioningStatsPullTimestampMillis = getWallTimeMillis();
1194             RcsClientProvisioningStats[] previousStats = mAtoms.rcsClientProvisioningStats;
1195             mAtoms.rcsClientProvisioningStats = new RcsClientProvisioningStats[0];
1196             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1197             return previousStats;
1198         } else {
1199             return null;
1200         }
1201     }
1202 
1203     /**
1204      * Returns and clears the RcsAcsProvisioningStats normalized to 24h cycle if last pulled
1205      * longer than {@code minIntervalMillis} ago, otherwise returns {@code null}.
1206      */
1207     @Nullable
getRcsAcsProvisioningStats( long minIntervalMillis)1208     public synchronized RcsAcsProvisioningStats[] getRcsAcsProvisioningStats(
1209             long minIntervalMillis) {
1210         long intervalMillis =
1211                 getWallTimeMillis() - mAtoms.rcsAcsProvisioningStatsPullTimestampMillis;
1212         if (intervalMillis > minIntervalMillis) {
1213             mAtoms.rcsAcsProvisioningStatsPullTimestampMillis = getWallTimeMillis();
1214             RcsAcsProvisioningStats[] previousStats = mAtoms.rcsAcsProvisioningStats;
1215 
1216             for (RcsAcsProvisioningStats stat: previousStats) {
1217                 // in case pull interval is greater than 24H, normalize it as of one day interval
1218                 if (intervalMillis > DAY_IN_MILLIS) {
1219                     stat.stateTimerMillis = normalizeDurationTo24H(stat.stateTimerMillis,
1220                             intervalMillis);
1221                 }
1222             }
1223 
1224             mAtoms.rcsAcsProvisioningStats = new RcsAcsProvisioningStats[0];
1225             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1226             return previousStats;
1227         } else {
1228             return null;
1229         }
1230     }
1231 
1232     /**
1233      * Returns and clears the SipDelegateStats if last pulled longer than {@code
1234      * minIntervalMillis} ago, otherwise returns {@code null}.
1235      */
1236     @Nullable
getSipDelegateStats(long minIntervalMillis)1237     public synchronized SipDelegateStats[] getSipDelegateStats(long minIntervalMillis) {
1238         long intervalMillis = getWallTimeMillis() - mAtoms.sipDelegateStatsPullTimestampMillis;
1239         if (intervalMillis > minIntervalMillis) {
1240             mAtoms.sipDelegateStatsPullTimestampMillis = getWallTimeMillis();
1241             SipDelegateStats[] previousStats = mAtoms.sipDelegateStats;
1242 
1243             for (SipDelegateStats stat: previousStats) {
1244                 // in case pull interval is greater than 24H, normalize it as of one day interval
1245                 if (intervalMillis > DAY_IN_MILLIS) {
1246                     stat.uptimeMillis = normalizeDurationTo24H(stat.uptimeMillis,
1247                             intervalMillis);
1248                 }
1249             }
1250 
1251             mAtoms.sipDelegateStats = new SipDelegateStats[0];
1252             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1253             return previousStats;
1254         } else {
1255             return null;
1256         }
1257     }
1258 
1259     /**
1260      * Returns and clears the SipTransportFeatureTagStats if last pulled longer than {@code
1261      * minIntervalMillis} ago, otherwise returns {@code null}.
1262      */
1263     @Nullable
getSipTransportFeatureTagStats( long minIntervalMillis)1264     public synchronized SipTransportFeatureTagStats[] getSipTransportFeatureTagStats(
1265             long minIntervalMillis) {
1266         long intervalMillis =
1267                 getWallTimeMillis() - mAtoms.sipTransportFeatureTagStatsPullTimestampMillis;
1268         if (intervalMillis > minIntervalMillis) {
1269             mAtoms.sipTransportFeatureTagStatsPullTimestampMillis = getWallTimeMillis();
1270             SipTransportFeatureTagStats[] previousStats = mAtoms.sipTransportFeatureTagStats;
1271 
1272             for (SipTransportFeatureTagStats stat: previousStats) {
1273                 // in case pull interval is greater than 24H, normalize it as of one day interval
1274                 if (intervalMillis > DAY_IN_MILLIS) {
1275                     stat.associatedMillis = normalizeDurationTo24H(stat.associatedMillis,
1276                             intervalMillis);
1277                 }
1278             }
1279 
1280             mAtoms.sipTransportFeatureTagStats = new SipTransportFeatureTagStats[0];
1281             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1282             return previousStats;
1283         } else {
1284             return null;
1285         }
1286     }
1287 
1288     /**
1289      * Returns and clears the SipMessageResponse if last pulled longer than {@code
1290      * minIntervalMillis} ago, otherwise returns {@code null}.
1291      */
1292     @Nullable
getSipMessageResponse(long minIntervalMillis)1293     public synchronized SipMessageResponse[] getSipMessageResponse(long minIntervalMillis) {
1294         if (getWallTimeMillis() - mAtoms.sipMessageResponsePullTimestampMillis
1295                 > minIntervalMillis) {
1296             mAtoms.sipMessageResponsePullTimestampMillis = getWallTimeMillis();
1297             SipMessageResponse[] previousStats =
1298                     mAtoms.sipMessageResponse;
1299             mAtoms.sipMessageResponse = new SipMessageResponse[0];
1300             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1301             return previousStats;
1302         } else {
1303             return null;
1304         }
1305     }
1306 
1307     /**
1308      * Returns and clears the SipTransportSession if last pulled longer than {@code
1309      * minIntervalMillis} ago, otherwise returns {@code null}.
1310      */
1311     @Nullable
getSipTransportSession(long minIntervalMillis)1312     public synchronized SipTransportSession[] getSipTransportSession(long minIntervalMillis) {
1313         if (getWallTimeMillis() - mAtoms.sipTransportSessionPullTimestampMillis
1314                 > minIntervalMillis) {
1315             mAtoms.sipTransportSessionPullTimestampMillis = getWallTimeMillis();
1316             SipTransportSession[] previousStats =
1317                     mAtoms.sipTransportSession;
1318             mAtoms.sipTransportSession = new SipTransportSession[0];
1319             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1320             return previousStats;
1321         } else {
1322             return null;
1323         }
1324     }
1325 
1326     /**
1327      * Returns and clears the ImsDedicatedBearerListenerEvent if last pulled longer than {@code
1328      * minIntervalMillis} ago, otherwise returns {@code null}.
1329      */
1330     @Nullable
getImsDedicatedBearerListenerEvent( long minIntervalMillis)1331     public synchronized ImsDedicatedBearerListenerEvent[] getImsDedicatedBearerListenerEvent(
1332             long minIntervalMillis) {
1333         if (getWallTimeMillis() - mAtoms.imsDedicatedBearerListenerEventPullTimestampMillis
1334                 > minIntervalMillis) {
1335             mAtoms.imsDedicatedBearerListenerEventPullTimestampMillis = getWallTimeMillis();
1336             ImsDedicatedBearerListenerEvent[] previousStats =
1337                 mAtoms.imsDedicatedBearerListenerEvent;
1338             mAtoms.imsDedicatedBearerListenerEvent = new ImsDedicatedBearerListenerEvent[0];
1339             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1340             return previousStats;
1341         } else {
1342             return null;
1343         }
1344     }
1345 
1346     /**
1347      * Returns and clears the ImsDedicatedBearerEvent if last pulled longer than {@code
1348      * minIntervalMillis} ago, otherwise returns {@code null}.
1349      */
1350     @Nullable
getImsDedicatedBearerEvent( long minIntervalMillis)1351     public synchronized ImsDedicatedBearerEvent[] getImsDedicatedBearerEvent(
1352             long minIntervalMillis) {
1353         if (getWallTimeMillis() - mAtoms.imsDedicatedBearerEventPullTimestampMillis
1354                   > minIntervalMillis) {
1355             mAtoms.imsDedicatedBearerEventPullTimestampMillis = getWallTimeMillis();
1356             ImsDedicatedBearerEvent[] previousStats =
1357                 mAtoms.imsDedicatedBearerEvent;
1358             mAtoms.imsDedicatedBearerEvent = new ImsDedicatedBearerEvent[0];
1359             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1360             return previousStats;
1361         } else {
1362             return null;
1363         }
1364     }
1365 
1366     /**
1367      * Returns and clears the ImsRegistrationServiceDescStats if last pulled longer than {@code
1368      * minIntervalMillis} ago, otherwise returns {@code null}.
1369      */
1370     @Nullable
getImsRegistrationServiceDescStats(long minIntervalMillis)1371     public synchronized ImsRegistrationServiceDescStats[] getImsRegistrationServiceDescStats(long
1372             minIntervalMillis) {
1373         long intervalMillis =
1374                 getWallTimeMillis() - mAtoms.imsRegistrationServiceDescStatsPullTimestampMillis;
1375         if (intervalMillis > minIntervalMillis) {
1376             mAtoms.imsRegistrationServiceDescStatsPullTimestampMillis = getWallTimeMillis();
1377             ImsRegistrationServiceDescStats[] previousStats =
1378                 mAtoms.imsRegistrationServiceDescStats;
1379 
1380             for (ImsRegistrationServiceDescStats stat: previousStats) {
1381                 // in case pull interval is greater than 24H, normalize it as of one day interval
1382                 if (intervalMillis > DAY_IN_MILLIS) {
1383                     stat.publishedMillis = normalizeDurationTo24H(stat.publishedMillis,
1384                             intervalMillis);
1385                 }
1386             }
1387 
1388             mAtoms.imsRegistrationServiceDescStats = new ImsRegistrationServiceDescStats[0];
1389             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1390             return previousStats;
1391         } else {
1392             return null;
1393         }
1394     }
1395 
1396     /**
1397      * Returns and clears the UceEventStats if last pulled longer than {@code
1398      * minIntervalMillis} ago, otherwise returns {@code null}.
1399      */
1400     @Nullable
getUceEventStats(long minIntervalMillis)1401     public synchronized UceEventStats[] getUceEventStats(long minIntervalMillis) {
1402         if (getWallTimeMillis() - mAtoms.uceEventStatsPullTimestampMillis > minIntervalMillis) {
1403             mAtoms.uceEventStatsPullTimestampMillis = getWallTimeMillis();
1404             UceEventStats[] previousStats = mAtoms.uceEventStats;
1405             mAtoms.uceEventStats = new UceEventStats[0];
1406             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1407             return previousStats;
1408         } else {
1409             return null;
1410         }
1411     }
1412 
1413     /**
1414      * Returns and clears the PresenceNotifyEvent if last pulled longer than {@code
1415      * minIntervalMillis} ago, otherwise returns {@code null}.
1416      */
1417     @Nullable
getPresenceNotifyEvent(long minIntervalMillis)1418     public synchronized PresenceNotifyEvent[] getPresenceNotifyEvent(long minIntervalMillis) {
1419         if (getWallTimeMillis() - mAtoms.presenceNotifyEventPullTimestampMillis
1420                 > minIntervalMillis) {
1421             mAtoms.presenceNotifyEventPullTimestampMillis = getWallTimeMillis();
1422             PresenceNotifyEvent[] previousStats = mAtoms.presenceNotifyEvent;
1423             mAtoms.presenceNotifyEvent = new PresenceNotifyEvent[0];
1424             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1425             return previousStats;
1426         } else {
1427             return null;
1428         }
1429     }
1430 
1431     /**
1432      * Returns and clears the GbaEvent if last pulled longer than {@code
1433      * minIntervalMillis} ago, otherwise returns {@code null}.
1434      */
1435     @Nullable
getGbaEvent(long minIntervalMillis)1436     public synchronized GbaEvent[] getGbaEvent(long minIntervalMillis) {
1437         if (getWallTimeMillis() - mAtoms.gbaEventPullTimestampMillis > minIntervalMillis) {
1438             mAtoms.gbaEventPullTimestampMillis = getWallTimeMillis();
1439             GbaEvent[] previousStats = mAtoms.gbaEvent;
1440             mAtoms.gbaEvent = new GbaEvent[0];
1441             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1442             return previousStats;
1443         } else {
1444             return null;
1445         }
1446     }
1447 
1448     /**
1449      *  Returns the unmetered networks bitmask for a given phone id. Returns 0 if there is
1450      *  no existing UnmeteredNetworks for the given phone id or the carrier id doesn't match.
1451      *  Existing UnmeteredNetworks is discarded after.
1452      */
getUnmeteredNetworks(int phoneId, int carrierId)1453     public synchronized @NetworkTypeBitMask long getUnmeteredNetworks(int phoneId, int carrierId) {
1454         UnmeteredNetworks existingStats = findUnmeteredNetworks(phoneId);
1455         if (existingStats == null) {
1456             return 0L;
1457         }
1458         @NetworkTypeBitMask
1459         long bitmask =
1460                 existingStats.carrierId != carrierId ? 0L : existingStats.unmeteredNetworksBitmask;
1461         mAtoms.unmeteredNetworks =
1462                 sanitizeAtoms(
1463                         ArrayUtils.removeElement(
1464                                 UnmeteredNetworks.class,
1465                                 mAtoms.unmeteredNetworks,
1466                                 existingStats),
1467                         UnmeteredNetworks.class);
1468         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1469         return bitmask;
1470     }
1471 
1472     /**
1473      * Returns and clears the OutgoingShortCodeSms if last pulled longer than {@code
1474      * minIntervalMillis} ago, otherwise returns {@code null}.
1475      */
1476     @Nullable
getOutgoingShortCodeSms(long minIntervalMillis)1477     public synchronized OutgoingShortCodeSms[] getOutgoingShortCodeSms(long minIntervalMillis) {
1478         if ((getWallTimeMillis() - mAtoms.outgoingShortCodeSmsPullTimestampMillis)
1479                 > minIntervalMillis) {
1480             mAtoms.outgoingShortCodeSmsPullTimestampMillis = getWallTimeMillis();
1481             OutgoingShortCodeSms[] previousOutgoingShortCodeSms = mAtoms.outgoingShortCodeSms;
1482             mAtoms.outgoingShortCodeSms = new OutgoingShortCodeSms[0];
1483             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1484             return previousOutgoingShortCodeSms;
1485         } else {
1486             return null;
1487         }
1488     }
1489 
1490     /**
1491      * Returns and clears the {@link SatelliteController} stats if last pulled longer than {@code
1492      * minIntervalMillis} ago, otherwise returns {@code null}.
1493      */
1494     @Nullable
getSatelliteControllerStats(long minIntervalMillis)1495     public synchronized SatelliteController[] getSatelliteControllerStats(long minIntervalMillis) {
1496         if (getWallTimeMillis() - mAtoms.satelliteControllerPullTimestampMillis
1497                 > minIntervalMillis) {
1498             mAtoms.satelliteControllerPullTimestampMillis = getWallTimeMillis();
1499             SatelliteController[] statsArray = mAtoms.satelliteController;
1500             mAtoms.satelliteController = new SatelliteController[0];
1501             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1502             return statsArray;
1503         } else {
1504             return null;
1505         }
1506     }
1507 
1508     /**
1509      * Returns and clears the {@link SatelliteSession} stats if last pulled longer than {@code
1510      * minIntervalMillis} ago, otherwise returns {@code null}.
1511      */
1512     @Nullable
getSatelliteSessionStats(long minIntervalMillis)1513     public synchronized SatelliteSession[] getSatelliteSessionStats(long minIntervalMillis) {
1514         if (getWallTimeMillis() - mAtoms.satelliteSessionPullTimestampMillis
1515                 > minIntervalMillis) {
1516             mAtoms.satelliteSessionPullTimestampMillis = getWallTimeMillis();
1517             SatelliteSession[] statsArray = mAtoms.satelliteSession;
1518             mAtoms.satelliteSession = new SatelliteSession[0];
1519             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1520             return statsArray;
1521         } else {
1522             return null;
1523         }
1524     }
1525 
1526     /**
1527      * Returns and clears the {@link SatelliteIncomingDatagram} stats if last pulled longer than
1528      * {@code minIntervalMillis} ago, otherwise returns {@code null}.
1529      */
1530     @Nullable
getSatelliteIncomingDatagramStats( long minIntervalMillis)1531     public synchronized SatelliteIncomingDatagram[] getSatelliteIncomingDatagramStats(
1532             long minIntervalMillis) {
1533         if (getWallTimeMillis() - mAtoms.satelliteIncomingDatagramPullTimestampMillis
1534                 > minIntervalMillis) {
1535             mAtoms.satelliteIncomingDatagramPullTimestampMillis = getWallTimeMillis();
1536             SatelliteIncomingDatagram[] statsArray = mAtoms.satelliteIncomingDatagram;
1537             mAtoms.satelliteIncomingDatagram = new SatelliteIncomingDatagram[0];
1538             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1539             return statsArray;
1540         } else {
1541             return null;
1542         }
1543     }
1544 
1545     /**
1546      * Returns and clears the {@link SatelliteOutgoingDatagram} stats if last pulled longer than
1547      * {@code minIntervalMillis} ago, otherwise returns {@code null}.
1548      */
1549     @Nullable
getSatelliteOutgoingDatagramStats( long minIntervalMillis)1550     public synchronized SatelliteOutgoingDatagram[] getSatelliteOutgoingDatagramStats(
1551             long minIntervalMillis) {
1552         if (getWallTimeMillis() - mAtoms.satelliteOutgoingDatagramPullTimestampMillis
1553                 > minIntervalMillis) {
1554             mAtoms.satelliteOutgoingDatagramPullTimestampMillis = getWallTimeMillis();
1555             SatelliteOutgoingDatagram[] statsArray = mAtoms.satelliteOutgoingDatagram;
1556             mAtoms.satelliteOutgoingDatagram = new SatelliteOutgoingDatagram[0];
1557             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1558             return statsArray;
1559         } else {
1560             return null;
1561         }
1562     }
1563 
1564     /**
1565      * Returns and clears the {@link SatelliteProvision} stats if last pulled longer than {@code
1566      * minIntervalMillis} ago, otherwise returns {@code null}.
1567      */
1568     @Nullable
getSatelliteProvisionStats(long minIntervalMillis)1569     public synchronized SatelliteProvision[] getSatelliteProvisionStats(long minIntervalMillis) {
1570         if (getWallTimeMillis() - mAtoms.satelliteProvisionPullTimestampMillis
1571                 > minIntervalMillis) {
1572             mAtoms.satelliteProvisionPullTimestampMillis = getWallTimeMillis();
1573             SatelliteProvision[] statsArray = mAtoms.satelliteProvision;
1574             mAtoms.satelliteProvision = new SatelliteProvision[0];
1575             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1576             return statsArray;
1577         } else {
1578             return null;
1579         }
1580     }
1581 
1582     /**
1583      * Returns and clears the {@link SatelliteSosMessageRecommender} stats if last pulled longer
1584      * than {@code minIntervalMillis} ago, otherwise returns {@code null}.
1585      */
1586     @Nullable
getSatelliteSosMessageRecommenderStats( long minIntervalMillis)1587     public synchronized SatelliteSosMessageRecommender[] getSatelliteSosMessageRecommenderStats(
1588             long minIntervalMillis) {
1589         if (getWallTimeMillis() - mAtoms.satelliteSosMessageRecommenderPullTimestampMillis
1590                 > minIntervalMillis) {
1591             mAtoms.satelliteSosMessageRecommenderPullTimestampMillis = getWallTimeMillis();
1592             SatelliteSosMessageRecommender[] statsArray = mAtoms.satelliteSosMessageRecommender;
1593             mAtoms.satelliteSosMessageRecommender = new SatelliteSosMessageRecommender[0];
1594             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1595             return statsArray;
1596         } else {
1597             return null;
1598         }
1599     }
1600 
1601     /**
1602      * Returns and clears the data network validation if last pulled longer than {@code
1603      * minIntervalMillis} ago, otherwise returns {@code null}.
1604      */
1605     @Nullable
getDataNetworkValidation(long minIntervalMillis)1606     public synchronized DataNetworkValidation[] getDataNetworkValidation(long minIntervalMillis) {
1607         long wallTime = getWallTimeMillis();
1608         if (wallTime - mAtoms.dataNetworkValidationPullTimestampMillis > minIntervalMillis) {
1609             mAtoms.dataNetworkValidationPullTimestampMillis = wallTime;
1610             DataNetworkValidation[] previousDataNetworkValidation = mAtoms.dataNetworkValidation;
1611             mAtoms.dataNetworkValidation = new DataNetworkValidation[0];
1612             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1613             return previousDataNetworkValidation;
1614         } else {
1615             return null;
1616         }
1617     }
1618 
1619     /**
1620      * Returns and clears the {@link CarrierRoamingSatelliteSession} stats if last pulled
1621      * longer than {@code minIntervalMillis} ago, otherwise returns {@code null}.
1622      */
1623     @Nullable
getCarrierRoamingSatelliteSessionStats( long minIntervalMillis)1624     public synchronized CarrierRoamingSatelliteSession[] getCarrierRoamingSatelliteSessionStats(
1625             long minIntervalMillis) {
1626         if (getWallTimeMillis() - mAtoms.carrierRoamingSatelliteSessionPullTimestampMillis
1627                 > minIntervalMillis) {
1628             mAtoms.carrierRoamingSatelliteSessionPullTimestampMillis = getWallTimeMillis();
1629             CarrierRoamingSatelliteSession[] statsArray = mAtoms.carrierRoamingSatelliteSession;
1630             mAtoms.carrierRoamingSatelliteSession = new CarrierRoamingSatelliteSession[0];
1631             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1632             return statsArray;
1633         } else {
1634             return null;
1635         }
1636     }
1637 
1638     /**
1639      * Returns and clears the {@link CarrierRoamingSatelliteControllerStats} stats if last pulled
1640      * longer than {@code minIntervalMillis} ago, otherwise returns {@code null}.
1641      */
1642     @Nullable
1643     public synchronized CarrierRoamingSatelliteControllerStats[]
getCarrierRoamingSatelliteControllerStats(long minIntervalMillis)1644             getCarrierRoamingSatelliteControllerStats(long minIntervalMillis) {
1645         if (getWallTimeMillis() - mAtoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis
1646                 > minIntervalMillis) {
1647             mAtoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = getWallTimeMillis();
1648             CarrierRoamingSatelliteControllerStats[] statsArray =
1649                     mAtoms.carrierRoamingSatelliteControllerStats;
1650             mAtoms.carrierRoamingSatelliteControllerStats =
1651                     new CarrierRoamingSatelliteControllerStats[0];
1652             SatelliteStats.getInstance().resetCarrierRoamingSatelliteControllerStats();
1653             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1654             return statsArray;
1655         } else {
1656             return null;
1657         }
1658     }
1659 
1660     /**
1661      * Returns and clears the {@link SatelliteEntitlement} stats if last pulled longer than {@code
1662      * minIntervalMillis} ago, otherwise returns {@code null}.
1663      */
1664     @Nullable
getSatelliteEntitlementStats( long minIntervalMillis)1665     public synchronized SatelliteEntitlement[] getSatelliteEntitlementStats(
1666             long minIntervalMillis) {
1667         if (getWallTimeMillis() - mAtoms.satelliteEntitlementPullTimestampMillis
1668                 > minIntervalMillis) {
1669             mAtoms.satelliteEntitlementPullTimestampMillis = getWallTimeMillis();
1670             SatelliteEntitlement[] statsArray = mAtoms.satelliteEntitlement;
1671             mAtoms.satelliteEntitlement = new SatelliteEntitlement[0];
1672             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1673             return statsArray;
1674         } else {
1675             return null;
1676         }
1677     }
1678 
1679     /**
1680      * Returns and clears the {@link SatelliteConfigUpdater} stats if last pulled longer than {@code
1681      * minIntervalMillis} ago, otherwise returns {@code null}.
1682      */
1683     @Nullable
getSatelliteConfigUpdaterStats( long minIntervalMillis)1684     public synchronized SatelliteConfigUpdater[] getSatelliteConfigUpdaterStats(
1685             long minIntervalMillis) {
1686         if (getWallTimeMillis() - mAtoms.satelliteConfigUpdaterPullTimestampMillis
1687                 > minIntervalMillis) {
1688             mAtoms.satelliteConfigUpdaterPullTimestampMillis = getWallTimeMillis();
1689             SatelliteConfigUpdater[] statsArray = mAtoms.satelliteConfigUpdater;
1690             mAtoms.satelliteConfigUpdater = new SatelliteConfigUpdater[0];
1691             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1692             return statsArray;
1693         } else {
1694             return null;
1695         }
1696     }
1697 
1698     /**
1699      * Returns and clears the {@link SatelliteAccessController} stats if last pulled longer
1700      * than {@code minIntervalMillis} ago, otherwise returns {@code null}.
1701      */
1702     @Nullable
getSatelliteAccessControllerStats( long minIntervalMillis)1703     public synchronized SatelliteAccessController[] getSatelliteAccessControllerStats(
1704             long minIntervalMillis) {
1705         if (getWallTimeMillis() - mAtoms.satelliteAccessControllerPullTimestampMillis
1706                 > minIntervalMillis) {
1707             mAtoms.satelliteAccessControllerPullTimestampMillis = getWallTimeMillis();
1708             SatelliteAccessController[] statsArray = mAtoms.satelliteAccessController;
1709             mAtoms.satelliteAccessController = new SatelliteAccessController[0];
1710             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
1711             return statsArray;
1712         } else {
1713             return null;
1714         }
1715     }
1716 
1717     /** Saves {@link PersistAtoms} to a file in private storage immediately. */
flushAtoms()1718     public synchronized void flushAtoms() {
1719         saveAtomsToFile(0);
1720     }
1721 
1722     /** Clears atoms for testing purpose. */
clearAtoms()1723     public synchronized void clearAtoms() {
1724         mAtoms = makeNewPersistAtoms();
1725         saveAtomsToFile(0);
1726     }
1727 
1728     /** Loads {@link PersistAtoms} from a file in private storage. */
loadAtomsFromFile()1729     private PersistAtoms loadAtomsFromFile() {
1730         try {
1731             PersistAtoms atoms =
1732                     PersistAtoms.parseFrom(
1733                             Files.readAllBytes(mContext.getFileStreamPath(FILENAME).toPath()));
1734             // Start from scratch if build changes, since mixing atoms from different builds could
1735             // produce strange results
1736             if (!Build.FINGERPRINT.equals(atoms.buildFingerprint)) {
1737                 Rlog.d(TAG, "Build changed");
1738                 return makeNewPersistAtoms();
1739             }
1740             // check all the fields in case of situations such as OTA or crash during saving
1741             atoms.voiceCallRatUsage =
1742                     sanitizeAtoms(atoms.voiceCallRatUsage, VoiceCallRatUsage.class);
1743             atoms.voiceCallSession =
1744                     sanitizeAtoms(
1745                             atoms.voiceCallSession,
1746                             VoiceCallSession.class,
1747                             mMaxNumVoiceCallSessions);
1748             atoms.incomingSms = sanitizeAtoms(atoms.incomingSms, IncomingSms.class, mMaxNumSms);
1749             atoms.outgoingSms = sanitizeAtoms(atoms.outgoingSms, OutgoingSms.class, mMaxNumSms);
1750             atoms.carrierIdMismatch =
1751                     sanitizeAtoms(
1752                             atoms.carrierIdMismatch,
1753                             CarrierIdMismatch.class,
1754                             mMaxNumCarrierIdMismatches);
1755             atoms.dataCallSession =
1756                     sanitizeAtoms(
1757                             atoms.dataCallSession,
1758                             DataCallSession.class,
1759                             mMaxNumDataCallSessions);
1760             atoms.cellularServiceState =
1761                     sanitizeAtoms(
1762                             atoms.cellularServiceState,
1763                             CellularServiceState.class,
1764                             mMaxNumCellularServiceStates);
1765             atoms.cellularDataServiceSwitch =
1766                     sanitizeAtoms(
1767                             atoms.cellularDataServiceSwitch,
1768                             CellularDataServiceSwitch.class,
1769                             mMaxNumCellularDataSwitches);
1770             atoms.imsRegistrationStats =
1771                     sanitizeAtoms(
1772                             atoms.imsRegistrationStats,
1773                             ImsRegistrationStats.class,
1774                             mMaxNumImsRegistrationStats);
1775             atoms.imsRegistrationTermination =
1776                     sanitizeAtoms(
1777                             atoms.imsRegistrationTermination,
1778                             ImsRegistrationTermination.class,
1779                             mMaxNumImsRegistrationTerminations);
1780             atoms.networkRequestsV2 =
1781                     sanitizeAtoms(atoms.networkRequestsV2, NetworkRequestsV2.class);
1782             atoms.imsRegistrationFeatureTagStats =
1783                     sanitizeAtoms(
1784                             atoms.imsRegistrationFeatureTagStats,
1785                             ImsRegistrationFeatureTagStats.class,
1786                             mMaxNumImsRegistrationFeatureStats);
1787             atoms.rcsClientProvisioningStats =
1788                     sanitizeAtoms(
1789                             atoms.rcsClientProvisioningStats,
1790                             RcsClientProvisioningStats.class,
1791                             mMaxNumRcsClientProvisioningStats);
1792             atoms.rcsAcsProvisioningStats =
1793                     sanitizeAtoms(
1794                             atoms.rcsAcsProvisioningStats,
1795                             RcsAcsProvisioningStats.class,
1796                             mMaxNumRcsAcsProvisioningStats);
1797             atoms.sipDelegateStats =
1798                     sanitizeAtoms(
1799                             atoms.sipDelegateStats,
1800                             SipDelegateStats.class,
1801                             mMaxNumSipDelegateStats);
1802             atoms.sipTransportFeatureTagStats =
1803                     sanitizeAtoms(
1804                             atoms.sipTransportFeatureTagStats,
1805                             SipTransportFeatureTagStats.class,
1806                             mMaxNumSipTransportFeatureTagStats);
1807             atoms.sipMessageResponse =
1808                     sanitizeAtoms(
1809                             atoms.sipMessageResponse,
1810                             SipMessageResponse.class,
1811                             mMaxNumSipMessageResponseStats);
1812             atoms.sipTransportSession =
1813                     sanitizeAtoms(
1814                             atoms.sipTransportSession,
1815                             SipTransportSession.class,
1816                             mMaxNumSipTransportSessionStats);
1817             atoms.imsDedicatedBearerListenerEvent =
1818                     sanitizeAtoms(
1819                             atoms.imsDedicatedBearerListenerEvent,
1820                             ImsDedicatedBearerListenerEvent.class,
1821                             mMaxNumDedicatedBearerListenerEventStats);
1822             atoms.imsDedicatedBearerEvent =
1823                     sanitizeAtoms(
1824                             atoms.imsDedicatedBearerEvent,
1825                             ImsDedicatedBearerEvent.class,
1826                             mMaxNumDedicatedBearerEventStats);
1827             atoms.imsRegistrationServiceDescStats =
1828                     sanitizeAtoms(
1829                             atoms.imsRegistrationServiceDescStats,
1830                             ImsRegistrationServiceDescStats.class,
1831                             mMaxNumImsRegistrationServiceDescStats);
1832             atoms.uceEventStats =
1833                     sanitizeAtoms(
1834                             atoms.uceEventStats,
1835                             UceEventStats.class,
1836                             mMaxNumUceEventStats);
1837             atoms.presenceNotifyEvent =
1838                     sanitizeAtoms(
1839                             atoms.presenceNotifyEvent,
1840                             PresenceNotifyEvent.class,
1841                             mMaxNumPresenceNotifyEventStats);
1842             atoms.gbaEvent =
1843                     sanitizeAtoms(
1844                             atoms.gbaEvent,
1845                             GbaEvent.class,
1846                             mMaxNumGbaEventStats);
1847             atoms.unmeteredNetworks =
1848                     sanitizeAtoms(
1849                             atoms.unmeteredNetworks,
1850                             UnmeteredNetworks.class
1851                     );
1852             atoms.outgoingShortCodeSms = sanitizeAtoms(atoms.outgoingShortCodeSms,
1853                     OutgoingShortCodeSms.class, mMaxOutgoingShortCodeSms);
1854             atoms.satelliteController = sanitizeAtoms(atoms.satelliteController,
1855                             SatelliteController.class, mMaxNumSatelliteStats);
1856             atoms.satelliteSession = sanitizeAtoms(atoms.satelliteSession,
1857                     SatelliteSession.class, mMaxNumSatelliteStats);
1858             atoms.satelliteIncomingDatagram = sanitizeAtoms(atoms.satelliteIncomingDatagram,
1859                             SatelliteIncomingDatagram.class, mMaxNumSatelliteStats);
1860             atoms.satelliteOutgoingDatagram = sanitizeAtoms(atoms.satelliteOutgoingDatagram,
1861                             SatelliteOutgoingDatagram.class, mMaxNumSatelliteStats);
1862             atoms.satelliteProvision = sanitizeAtoms(atoms.satelliteProvision,
1863                             SatelliteProvision.class, mMaxNumSatelliteStats);
1864             atoms.satelliteSosMessageRecommender = sanitizeAtoms(
1865                     atoms.satelliteSosMessageRecommender, SatelliteSosMessageRecommender.class,
1866                     mMaxNumSatelliteStats);
1867             atoms.dataNetworkValidation =
1868                     sanitizeAtoms(
1869                             atoms.dataNetworkValidation,
1870                             DataNetworkValidation.class,
1871                             mMaxNumDataNetworkValidation
1872                     );
1873             atoms.carrierRoamingSatelliteSession = sanitizeAtoms(
1874                     atoms.carrierRoamingSatelliteSession, CarrierRoamingSatelliteSession.class,
1875                     mMaxNumSatelliteStats);
1876             atoms.carrierRoamingSatelliteControllerStats = sanitizeAtoms(
1877                     atoms.carrierRoamingSatelliteControllerStats,
1878                     CarrierRoamingSatelliteControllerStats.class, mMaxNumSatelliteStats);
1879             atoms.satelliteEntitlement = sanitizeAtoms(atoms.satelliteEntitlement,
1880                     SatelliteEntitlement.class, mMaxNumSatelliteStats);
1881             atoms.satelliteConfigUpdater = sanitizeAtoms(atoms.satelliteConfigUpdater,
1882                     SatelliteConfigUpdater.class, mMaxNumSatelliteStats);
1883             atoms.satelliteAccessController = sanitizeAtoms(
1884                     atoms.satelliteAccessController, SatelliteAccessController.class,
1885                     mMaxNumSatelliteStats);
1886 
1887             // out of caution, sanitize also the timestamps
1888             atoms.voiceCallRatUsagePullTimestampMillis =
1889                     sanitizeTimestamp(atoms.voiceCallRatUsagePullTimestampMillis);
1890             atoms.voiceCallSessionPullTimestampMillis =
1891                     sanitizeTimestamp(atoms.voiceCallSessionPullTimestampMillis);
1892             atoms.incomingSmsPullTimestampMillis =
1893                     sanitizeTimestamp(atoms.incomingSmsPullTimestampMillis);
1894             atoms.outgoingSmsPullTimestampMillis =
1895                     sanitizeTimestamp(atoms.outgoingSmsPullTimestampMillis);
1896             atoms.dataCallSessionPullTimestampMillis =
1897                     sanitizeTimestamp(atoms.dataCallSessionPullTimestampMillis);
1898             atoms.cellularServiceStatePullTimestampMillis =
1899                     sanitizeTimestamp(atoms.cellularServiceStatePullTimestampMillis);
1900             atoms.cellularDataServiceSwitchPullTimestampMillis =
1901                     sanitizeTimestamp(atoms.cellularDataServiceSwitchPullTimestampMillis);
1902             atoms.imsRegistrationStatsPullTimestampMillis =
1903                     sanitizeTimestamp(atoms.imsRegistrationStatsPullTimestampMillis);
1904             atoms.imsRegistrationTerminationPullTimestampMillis =
1905                     sanitizeTimestamp(atoms.imsRegistrationTerminationPullTimestampMillis);
1906             atoms.networkRequestsV2PullTimestampMillis =
1907                     sanitizeTimestamp(atoms.networkRequestsV2PullTimestampMillis);
1908             atoms.imsRegistrationFeatureTagStatsPullTimestampMillis =
1909                     sanitizeTimestamp(atoms.imsRegistrationFeatureTagStatsPullTimestampMillis);
1910             atoms.rcsClientProvisioningStatsPullTimestampMillis =
1911                     sanitizeTimestamp(atoms.rcsClientProvisioningStatsPullTimestampMillis);
1912             atoms.rcsAcsProvisioningStatsPullTimestampMillis =
1913                     sanitizeTimestamp(atoms.rcsAcsProvisioningStatsPullTimestampMillis);
1914             atoms.sipDelegateStatsPullTimestampMillis =
1915                     sanitizeTimestamp(atoms.sipDelegateStatsPullTimestampMillis);
1916             atoms.sipTransportFeatureTagStatsPullTimestampMillis =
1917                     sanitizeTimestamp(atoms.sipTransportFeatureTagStatsPullTimestampMillis);
1918             atoms.sipMessageResponsePullTimestampMillis =
1919                     sanitizeTimestamp(atoms.sipMessageResponsePullTimestampMillis);
1920             atoms.sipTransportSessionPullTimestampMillis =
1921                     sanitizeTimestamp(atoms.sipTransportSessionPullTimestampMillis);
1922             atoms.imsDedicatedBearerListenerEventPullTimestampMillis =
1923                     sanitizeTimestamp(atoms.imsDedicatedBearerListenerEventPullTimestampMillis);
1924             atoms.imsDedicatedBearerEventPullTimestampMillis =
1925                     sanitizeTimestamp(atoms.imsDedicatedBearerEventPullTimestampMillis);
1926             atoms.imsRegistrationServiceDescStatsPullTimestampMillis =
1927                     sanitizeTimestamp(atoms.imsRegistrationServiceDescStatsPullTimestampMillis);
1928             atoms.uceEventStatsPullTimestampMillis =
1929                     sanitizeTimestamp(atoms.uceEventStatsPullTimestampMillis);
1930             atoms.presenceNotifyEventPullTimestampMillis =
1931                     sanitizeTimestamp(atoms.presenceNotifyEventPullTimestampMillis);
1932             atoms.gbaEventPullTimestampMillis =
1933                     sanitizeTimestamp(atoms.gbaEventPullTimestampMillis);
1934             atoms.outgoingShortCodeSmsPullTimestampMillis =
1935                     sanitizeTimestamp(atoms.outgoingShortCodeSmsPullTimestampMillis);
1936             atoms.satelliteControllerPullTimestampMillis =
1937                     sanitizeTimestamp(atoms.satelliteControllerPullTimestampMillis);
1938             atoms.satelliteSessionPullTimestampMillis =
1939                     sanitizeTimestamp(atoms.satelliteSessionPullTimestampMillis);
1940             atoms.satelliteIncomingDatagramPullTimestampMillis =
1941                     sanitizeTimestamp(atoms.satelliteIncomingDatagramPullTimestampMillis);
1942             atoms.satelliteOutgoingDatagramPullTimestampMillis =
1943                     sanitizeTimestamp(atoms.satelliteOutgoingDatagramPullTimestampMillis);
1944             atoms.satelliteProvisionPullTimestampMillis =
1945                     sanitizeTimestamp(atoms.satelliteProvisionPullTimestampMillis);
1946             atoms.satelliteSosMessageRecommenderPullTimestampMillis =
1947                     sanitizeTimestamp(atoms.satelliteSosMessageRecommenderPullTimestampMillis);
1948             atoms.dataNetworkValidationPullTimestampMillis =
1949                     sanitizeTimestamp(atoms.dataNetworkValidationPullTimestampMillis);
1950             atoms.carrierRoamingSatelliteSessionPullTimestampMillis = sanitizeTimestamp(
1951                     atoms.carrierRoamingSatelliteSessionPullTimestampMillis);
1952             atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = sanitizeTimestamp(
1953                     atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis);
1954             atoms.satelliteEntitlementPullTimestampMillis =
1955                     sanitizeTimestamp(atoms.satelliteEntitlementPullTimestampMillis);
1956             atoms.satelliteConfigUpdaterPullTimestampMillis =
1957                     sanitizeTimestamp(atoms.satelliteConfigUpdaterPullTimestampMillis);
1958             atoms.satelliteAccessControllerPullTimestampMillis =
1959                     sanitizeTimestamp(atoms.satelliteAccessControllerPullTimestampMillis);
1960             return atoms;
1961         } catch (NoSuchFileException e) {
1962             Rlog.d(TAG, "PersistAtoms file not found");
1963         } catch (IOException | NullPointerException e) {
1964             Rlog.e(TAG, "cannot load/parse PersistAtoms", e);
1965         }
1966         return makeNewPersistAtoms();
1967     }
1968 
1969     /**
1970      * Posts message to save a copy of {@link PersistAtoms} to a file after a delay or immediately.
1971      *
1972      * <p>The delay is introduced to avoid too frequent operations to disk, which would negatively
1973      * impact the power consumption.
1974      */
saveAtomsToFile(int delayMillis)1975     private synchronized void saveAtomsToFile(int delayMillis) {
1976         mHandler.removeCallbacks(mSaveRunnable);
1977         if (delayMillis > 0 && !mSaveImmediately) {
1978             if (mHandler.postDelayed(mSaveRunnable, delayMillis)) {
1979                 return;
1980             }
1981         }
1982         // In case of error posting the event or if delay is 0, save immediately
1983         saveAtomsToFileNow();
1984     }
1985 
1986     /** Saves a copy of {@link PersistAtoms} to a file in private storage. */
saveAtomsToFileNow()1987     private synchronized void saveAtomsToFileNow() {
1988         try (FileOutputStream stream = mContext.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
1989             stream.write(PersistAtoms.toByteArray(mAtoms));
1990         } catch (IOException e) {
1991             Rlog.e(TAG, "cannot save PersistAtoms", e);
1992         }
1993     }
1994 
1995     /**
1996      * Returns the service state that has the same dimension values with the given one, or {@code
1997      * null} if it does not exist.
1998      */
find(CellularServiceState key)1999     private @Nullable CellularServiceState find(CellularServiceState key) {
2000         for (CellularServiceState state : mAtoms.cellularServiceState) {
2001             if (state.voiceRat == key.voiceRat
2002                     && state.dataRat == key.dataRat
2003                     && state.voiceRoamingType == key.voiceRoamingType
2004                     && state.dataRoamingType == key.dataRoamingType
2005                     && state.isEndc == key.isEndc
2006                     && state.simSlotIndex == key.simSlotIndex
2007                     && state.isMultiSim == key.isMultiSim
2008                     && state.carrierId == key.carrierId
2009                     && state.isEmergencyOnly == key.isEmergencyOnly
2010                     && state.isInternetPdnUp == key.isInternetPdnUp
2011                     && state.foldState == key.foldState
2012                     && state.overrideVoiceService == key.overrideVoiceService
2013                     && state.isDataEnabled == key.isDataEnabled
2014                     && state.isIwlanCrossSim == key.isIwlanCrossSim
2015                     && state.isNtn == key.isNtn
2016                     && state.isNbIotNtn == key.isNbIotNtn
2017                     && state.isOpportunistic == key.isOpportunistic) {
2018                 return state;
2019             }
2020         }
2021         return null;
2022     }
2023 
2024     /**
2025      * Returns the data service switch that has the same dimension values with the given one, or
2026      * {@code null} if it does not exist.
2027      */
find(CellularDataServiceSwitch key)2028     private @Nullable CellularDataServiceSwitch find(CellularDataServiceSwitch key) {
2029         for (CellularDataServiceSwitch serviceSwitch : mAtoms.cellularDataServiceSwitch) {
2030             if (serviceSwitch.ratFrom == key.ratFrom
2031                     && serviceSwitch.ratTo == key.ratTo
2032                     && serviceSwitch.simSlotIndex == key.simSlotIndex
2033                     && serviceSwitch.isMultiSim == key.isMultiSim
2034                     && serviceSwitch.carrierId == key.carrierId
2035                     && serviceSwitch.isOpportunistic == key.isOpportunistic) {
2036                 return serviceSwitch;
2037             }
2038         }
2039         return null;
2040     }
2041 
2042     /**
2043      * Returns the carrier ID mismatch event that has the same dimension values with the given one,
2044      * or {@code null} if it does not exist.
2045      */
find(CarrierIdMismatch key)2046     private @Nullable CarrierIdMismatch find(CarrierIdMismatch key) {
2047         for (CarrierIdMismatch mismatch : mAtoms.carrierIdMismatch) {
2048             if (mismatch.mccMnc.equals(key.mccMnc)
2049                     && mismatch.gid1.equals(key.gid1)
2050                     && mismatch.spn.equals(key.spn)
2051                     && mismatch.pnn.equals(key.pnn)) {
2052                 return mismatch;
2053             }
2054         }
2055         return null;
2056     }
2057 
2058     /**
2059      * Returns the IMS registration stats that has the same dimension values with the given one, or
2060      * {@code null} if it does not exist.
2061      */
find(ImsRegistrationStats key)2062     private @Nullable ImsRegistrationStats find(ImsRegistrationStats key) {
2063         for (ImsRegistrationStats stats : mAtoms.imsRegistrationStats) {
2064             if (stats.carrierId == key.carrierId
2065                     && stats.simSlotIndex == key.simSlotIndex
2066                     && stats.rat == key.rat
2067                     && stats.isIwlanCrossSim == key.isIwlanCrossSim) {
2068                 return stats;
2069             }
2070         }
2071         return null;
2072     }
2073 
2074     /**
2075      * Returns the IMS registration termination that has the same dimension values with the given
2076      * one, or {@code null} if it does not exist.
2077      */
find(ImsRegistrationTermination key)2078     private @Nullable ImsRegistrationTermination find(ImsRegistrationTermination key) {
2079         for (ImsRegistrationTermination termination : mAtoms.imsRegistrationTermination) {
2080             if (termination.carrierId == key.carrierId
2081                     && termination.isMultiSim == key.isMultiSim
2082                     && termination.ratAtEnd == key.ratAtEnd
2083                     && termination.isIwlanCrossSim == key.isIwlanCrossSim
2084                     && termination.setupFailed == key.setupFailed
2085                     && termination.reasonCode == key.reasonCode
2086                     && termination.extraCode == key.extraCode
2087                     && termination.extraMessage.equals(key.extraMessage)) {
2088                 return termination;
2089             }
2090         }
2091         return null;
2092     }
2093 
2094     /**
2095      * Returns the network requests event that has the same carrier id and capability as the given
2096      * one, or {@code null} if it does not exist.
2097      */
find(NetworkRequestsV2 key)2098     private @Nullable NetworkRequestsV2 find(NetworkRequestsV2 key) {
2099         for (NetworkRequestsV2 item : mAtoms.networkRequestsV2) {
2100             if (item.carrierId == key.carrierId && item.capability == key.capability) {
2101                 return item;
2102             }
2103         }
2104         return null;
2105     }
2106 
2107     /**
2108      * Returns the index of data call session that has the same random dimension as the given one,
2109      * or -1 if it does not exist.
2110      */
findIndex(DataCallSession key)2111     private int findIndex(DataCallSession key) {
2112         for (int i = 0; i < mAtoms.dataCallSession.length; i++) {
2113             if (mAtoms.dataCallSession[i].dimension == key.dimension) {
2114                 return i;
2115             }
2116         }
2117         return -1;
2118     }
2119     /**
2120      * Returns the Dedicated Bearer Listener event that has the same carrier id, slot id, rat, qci
2121      * and established state as the given one, or {@code null} if it does not exist.
2122      */
find(ImsDedicatedBearerListenerEvent key)2123     private @Nullable ImsDedicatedBearerListenerEvent find(ImsDedicatedBearerListenerEvent key) {
2124         for (ImsDedicatedBearerListenerEvent stats : mAtoms.imsDedicatedBearerListenerEvent) {
2125             if (stats.carrierId == key.carrierId
2126                     && stats.slotId == key.slotId
2127                     && stats.ratAtEnd == key.ratAtEnd
2128                     && stats.qci == key.qci
2129                     && stats.dedicatedBearerEstablished == key.dedicatedBearerEstablished) {
2130                 return stats;
2131             }
2132         }
2133         return null;
2134     }
2135 
2136     /**
2137      * Returns the Dedicated Bearer event that has the same carrier id, slot id, rat,
2138      * qci, bearer state, local/remote connection and exsting listener as the given one,
2139      * or {@code null} if it does not exist.
2140      */
find(ImsDedicatedBearerEvent key)2141     private @Nullable ImsDedicatedBearerEvent find(ImsDedicatedBearerEvent key) {
2142         for (ImsDedicatedBearerEvent stats : mAtoms.imsDedicatedBearerEvent) {
2143             if (stats.carrierId == key.carrierId
2144                     && stats.slotId == key.slotId
2145                     && stats.ratAtEnd == key.ratAtEnd
2146                     && stats.qci == key.qci
2147                     && stats.bearerState == key.bearerState
2148                     && stats.localConnectionInfoReceived == key.localConnectionInfoReceived
2149                     && stats.remoteConnectionInfoReceived == key.remoteConnectionInfoReceived
2150                     && stats.hasListeners == key.hasListeners) {
2151                 return stats;
2152             }
2153         }
2154         return null;
2155     }
2156 
2157     /**
2158      * Returns the Registration Feature Tag that has the same carrier id, slot id,
2159      * feature tag name or custom feature tag name and registration tech as the given one,
2160      * or {@code null} if it does not exist.
2161      */
find(ImsRegistrationFeatureTagStats key)2162     private @Nullable ImsRegistrationFeatureTagStats find(ImsRegistrationFeatureTagStats key) {
2163         for (ImsRegistrationFeatureTagStats stats : mAtoms.imsRegistrationFeatureTagStats) {
2164             if (stats.carrierId == key.carrierId
2165                     && stats.slotId == key.slotId
2166                     && stats.featureTagName == key.featureTagName
2167                     && stats.registrationTech == key.registrationTech) {
2168                 return stats;
2169             }
2170         }
2171         return null;
2172     }
2173 
2174     /**
2175      * Returns Client Provisioning that has the same carrier id, slot id and event as the given
2176      * one, or {@code null} if it does not exist.
2177      */
find(RcsClientProvisioningStats key)2178     private @Nullable RcsClientProvisioningStats find(RcsClientProvisioningStats key) {
2179         for (RcsClientProvisioningStats stats : mAtoms.rcsClientProvisioningStats) {
2180             if (stats.carrierId == key.carrierId
2181                     && stats.slotId == key.slotId
2182                     && stats.event == key.event) {
2183                 return stats;
2184             }
2185         }
2186         return null;
2187     }
2188 
2189     /**
2190      * Returns ACS Provisioning that has the same carrier id, slot id, response code, response type
2191      * and SR supported as the given one, or {@code null} if it does not exist.
2192      */
find(RcsAcsProvisioningStats key)2193     private @Nullable RcsAcsProvisioningStats find(RcsAcsProvisioningStats key) {
2194         for (RcsAcsProvisioningStats stats : mAtoms.rcsAcsProvisioningStats) {
2195             if (stats.carrierId == key.carrierId
2196                     && stats.slotId == key.slotId
2197                     && stats.responseCode == key.responseCode
2198                     && stats.responseType == key.responseType
2199                     && stats.isSingleRegistrationEnabled == key.isSingleRegistrationEnabled) {
2200                 return stats;
2201             }
2202         }
2203         return null;
2204     }
2205 
2206     /**
2207      * Returns Sip Message Response that has the same carrier id, slot id, method, response,
2208      * direction and error as the given one, or {@code null} if it does not exist.
2209      */
find(SipMessageResponse key)2210     private @Nullable SipMessageResponse find(SipMessageResponse key) {
2211         for (SipMessageResponse stats : mAtoms.sipMessageResponse) {
2212             if (stats.carrierId == key.carrierId
2213                     && stats.slotId == key.slotId
2214                     && stats.sipMessageMethod == key.sipMessageMethod
2215                     && stats.sipMessageResponse == key.sipMessageResponse
2216                     && stats.sipMessageDirection == key.sipMessageDirection
2217                     && stats.messageError == key.messageError) {
2218                 return stats;
2219             }
2220         }
2221         return null;
2222     }
2223 
2224     /**
2225      * Returns Sip Transport Session that has the same carrier id, slot id, method, direction and
2226      * response as the given one, or {@code null} if it does not exist.
2227      */
find(SipTransportSession key)2228     private @Nullable SipTransportSession find(SipTransportSession key) {
2229         for (SipTransportSession stats : mAtoms.sipTransportSession) {
2230             if (stats.carrierId == key.carrierId
2231                     && stats.slotId == key.slotId
2232                     && stats.sessionMethod == key.sessionMethod
2233                     && stats.sipMessageDirection == key.sipMessageDirection
2234                     && stats.sipResponse == key.sipResponse) {
2235                 return stats;
2236             }
2237         }
2238         return null;
2239     }
2240 
2241     /**
2242      * Returns Registration Service Desc Stats that has the same carrier id, slot id, service id or
2243      * custom service id, service id version and registration tech as the given one,
2244      * or {@code null} if it does not exist.
2245      */
find(ImsRegistrationServiceDescStats key)2246     private @Nullable ImsRegistrationServiceDescStats find(ImsRegistrationServiceDescStats key) {
2247         for (ImsRegistrationServiceDescStats stats : mAtoms.imsRegistrationServiceDescStats) {
2248             if (stats.carrierId == key.carrierId
2249                     && stats.slotId == key.slotId
2250                     && stats.serviceIdName == key.serviceIdName
2251                     && stats.serviceIdVersion == key.serviceIdVersion
2252                     && stats.registrationTech == key.registrationTech) {
2253                 return stats;
2254             }
2255         }
2256         return null;
2257     }
2258 
2259     /**
2260      * Returns UCE Event Stats that has the same carrier id, slot id, event result, command code and
2261      * network response as the given one, or {@code null} if it does not exist.
2262      */
find(UceEventStats key)2263     private @Nullable UceEventStats find(UceEventStats key) {
2264         for (UceEventStats stats : mAtoms.uceEventStats) {
2265             if (stats.carrierId == key.carrierId
2266                     && stats.slotId == key.slotId
2267                     && stats.type == key.type
2268                     && stats.successful == key.successful
2269                     && stats.commandCode == key.commandCode
2270                     && stats.networkResponse == key.networkResponse) {
2271                 return stats;
2272             }
2273         }
2274         return null;
2275     }
2276 
2277     /**
2278      * Returns Presence Notify Event that has the same carrier id, slot id, reason and body in
2279      * response as the given one, or {@code null} if it does not exist.
2280      */
find(PresenceNotifyEvent key)2281     private @Nullable PresenceNotifyEvent find(PresenceNotifyEvent key) {
2282         for (PresenceNotifyEvent stats : mAtoms.presenceNotifyEvent) {
2283             if (stats.carrierId == key.carrierId
2284                     && stats.slotId == key.slotId
2285                     && stats.reason == key.reason
2286                     && stats.contentBodyReceived == key.contentBodyReceived) {
2287                 return stats;
2288             }
2289         }
2290         return null;
2291     }
2292 
2293     /**
2294      * Returns GBA Event that has the same carrier id, slot id, result of operation and fail reason
2295      * as the given one, or {@code null} if it does not exist.
2296      */
find(GbaEvent key)2297     private @Nullable GbaEvent find(GbaEvent key) {
2298         for (GbaEvent stats : mAtoms.gbaEvent) {
2299             if (stats.carrierId == key.carrierId
2300                     && stats.slotId == key.slotId
2301                     && stats.successful == key.successful
2302                     && stats.failedReason == key.failedReason) {
2303                 return stats;
2304             }
2305         }
2306         return null;
2307     }
2308 
2309     /**
2310      * Returns Sip Transport Feature Tag Stats that has the same carrier id, slot id, feature tag
2311      * name, deregister reason, denied reason and feature tag name or custom feature tag name as
2312      * the given one, or {@code null} if it does not exist.
2313      */
find(SipTransportFeatureTagStats key)2314     private @Nullable SipTransportFeatureTagStats find(SipTransportFeatureTagStats key) {
2315         for (SipTransportFeatureTagStats stat : mAtoms.sipTransportFeatureTagStats) {
2316             if (stat.carrierId == key.carrierId
2317                     && stat.slotId == key.slotId
2318                     && stat.featureTagName == key.featureTagName
2319                     && stat.sipTransportDeregisteredReason == key.sipTransportDeregisteredReason
2320                     && stat.sipTransportDeniedReason == key.sipTransportDeniedReason) {
2321                 return stat;
2322             }
2323         }
2324         return null;
2325     }
2326 
2327     /** Returns the UnmeteredNetworks given a phone id. */
findUnmeteredNetworks(int phoneId)2328     private @Nullable UnmeteredNetworks findUnmeteredNetworks(int phoneId) {
2329         for (UnmeteredNetworks unmeteredNetworks : mAtoms.unmeteredNetworks) {
2330             if (unmeteredNetworks.phoneId == phoneId) {
2331                 return unmeteredNetworks;
2332             }
2333         }
2334         return null;
2335     }
2336 
2337     /**
2338      * Returns OutgoingShortCodeSms atom that has same category, xmlVersion as the given one,
2339      * or {@code null} if it does not exist.
2340      */
find(OutgoingShortCodeSms key)2341     private @Nullable OutgoingShortCodeSms find(OutgoingShortCodeSms key) {
2342         for (OutgoingShortCodeSms shortCodeSms : mAtoms.outgoingShortCodeSms) {
2343             if (shortCodeSms.category == key.category
2344                     && shortCodeSms.xmlVersion == key.xmlVersion) {
2345                 return shortCodeSms;
2346             }
2347         }
2348         return null;
2349     }
2350 
2351     /**
2352      * Returns SatelliteSession atom that has same values or {@code null}
2353      * if it does not exist.
2354      */
find( SatelliteSession key)2355     private @Nullable SatelliteSession find(
2356             SatelliteSession key) {
2357         for (SatelliteSession stats : mAtoms.satelliteSession) {
2358             if (stats.satelliteServiceInitializationResult
2359                     == key.satelliteServiceInitializationResult
2360                     && stats.satelliteTechnology == key.satelliteTechnology
2361                     && stats.satelliteServiceTerminationResult
2362                     == key.satelliteServiceTerminationResult
2363                     && stats.initializationProcessingTimeMillis
2364                     == key.initializationProcessingTimeMillis
2365                     && stats.terminationProcessingTimeMillis == key.terminationProcessingTimeMillis
2366                     && stats.sessionDurationSeconds == key.sessionDurationSeconds
2367                     && stats.countOfOutgoingDatagramSuccess == key.countOfOutgoingDatagramSuccess
2368                     && stats.countOfOutgoingDatagramFailed == key.countOfOutgoingDatagramFailed
2369                     && stats.countOfIncomingDatagramSuccess == key.countOfIncomingDatagramSuccess
2370                     && stats.countOfIncomingDatagramFailed == key.countOfIncomingDatagramFailed
2371                     && stats.isDemoMode == key.isDemoMode
2372                     && stats.maxNtnSignalStrengthLevel == key.maxNtnSignalStrengthLevel
2373                     && stats.carrierId == key.carrierId
2374                     && stats.countOfSatelliteNotificationDisplayed
2375                     == key.countOfSatelliteNotificationDisplayed
2376                     && stats.countOfAutoExitDueToScreenOff == key.countOfAutoExitDueToScreenOff
2377                     && stats.countOfAutoExitDueToTnNetwork == key.countOfAutoExitDueToTnNetwork
2378                     && stats.isEmergency == key.isEmergency
2379                     && stats.maxInactivityDurationSec == key.maxInactivityDurationSec) {
2380                 return stats;
2381             }
2382         }
2383         return null;
2384     }
2385 
2386     /**
2387      * Returns SatelliteSosMessageRecommender atom that has same values or {@code null}
2388      * if it does not exist.
2389      */
find( SatelliteSosMessageRecommender key)2390     private @Nullable SatelliteSosMessageRecommender find(
2391             SatelliteSosMessageRecommender key) {
2392         for (SatelliteSosMessageRecommender stats : mAtoms.satelliteSosMessageRecommender) {
2393             if (stats.isDisplaySosMessageSent == key.isDisplaySosMessageSent
2394                     && stats.countOfTimerStarted == key.countOfTimerStarted
2395                     && stats.isImsRegistered == key.isImsRegistered
2396                     && stats.cellularServiceState == key.cellularServiceState
2397                     && stats.isMultiSim == key.isMultiSim
2398                     && stats.recommendingHandoverType == key.recommendingHandoverType
2399                     && stats.isSatelliteAllowedInCurrentLocation
2400                     == key.isSatelliteAllowedInCurrentLocation
2401                     && stats.isWifiConnected == key.isWifiConnected
2402                     && stats.carrierId == key.carrierId) {
2403                 return stats;
2404             }
2405         }
2406         return null;
2407     }
2408 
2409     /**
2410      * Returns SatelliteOutgoingDatagram atom that has same values or {@code null}
2411      * if it does not exist.
2412      */
find(DataNetworkValidation key)2413     private @Nullable DataNetworkValidation find(DataNetworkValidation key) {
2414         for (DataNetworkValidation stats : mAtoms.dataNetworkValidation) {
2415             if (stats.networkType == key.networkType
2416                     && stats.apnTypeBitmask == key.apnTypeBitmask
2417                     && stats.signalStrength == key.signalStrength
2418                     && stats.validationResult == key.validationResult
2419                     && stats.handoverAttempted == key.handoverAttempted) {
2420                 return stats;
2421             }
2422         }
2423         return null;
2424     }
2425 
2426     /**
2427      * Returns the SatelliteController atom with the matching `carrier_id`, `is_provisioned`, and
2428      * `is_ntn_only_carrier` values, or {@code null} if does not exist.
2429      */
find(SatelliteController key)2430     private @Nullable SatelliteController find(SatelliteController key) {
2431         for (SatelliteController stats : mAtoms.satelliteController) {
2432             if (stats.carrierId == key.carrierId
2433                     && stats.isProvisioned == key.isProvisioned
2434                     && stats.isNtnOnlyCarrier == key.isNtnOnlyCarrier) {
2435                 return stats;
2436             }
2437         }
2438         return null;
2439     }
2440 
2441     /**
2442      * Returns CarrierRoamingSatelliteControllerStats atom that has same carrier_id value or
2443      * {@code null} if does not exist.
2444      */
find( CarrierRoamingSatelliteControllerStats key)2445     private @Nullable CarrierRoamingSatelliteControllerStats find(
2446             CarrierRoamingSatelliteControllerStats key) {
2447         for (CarrierRoamingSatelliteControllerStats stats :
2448                 mAtoms.carrierRoamingSatelliteControllerStats) {
2449             if (stats.carrierId == key.carrierId) {
2450                 return stats;
2451             }
2452         }
2453         return null;
2454     }
2455 
2456     /**
2457      * Returns SatelliteEntitlement atom that has same values or {@code null} if it does not exist.
2458      */
find(SatelliteEntitlement key)2459     private @Nullable SatelliteEntitlement find(SatelliteEntitlement key) {
2460         for (SatelliteEntitlement stats : mAtoms.satelliteEntitlement) {
2461             if (stats.carrierId == key.carrierId
2462                     && stats.result == key.result
2463                     && stats.entitlementStatus == key.entitlementStatus
2464                     && stats.isRetry == key.isRetry) {
2465                 return stats;
2466             }
2467         }
2468         return null;
2469     }
2470 
2471     /**
2472      * Returns SatelliteConfigUpdater atom that has same values
2473      * or {@code null} if it does not exist.
2474      */
find(SatelliteConfigUpdater key)2475     private @Nullable SatelliteConfigUpdater find(SatelliteConfigUpdater key) {
2476         for (SatelliteConfigUpdater stats : mAtoms.satelliteConfigUpdater) {
2477             if (stats.configVersion == key.configVersion
2478                     && stats.oemConfigResult == key.oemConfigResult
2479                     && stats.carrierConfigResult == key.carrierConfigResult) {
2480                 return stats;
2481             }
2482         }
2483         return null;
2484     }
2485 
2486     /**
2487      * Inserts a new element in a random position in an array with a maximum size.
2488      *
2489      * <p>If the array is full, merge with existing item if possible or replace one item randomly.
2490      */
insertAtRandomPlace(T[] storage, T instance, int maxLength)2491     private static <T> T[] insertAtRandomPlace(T[] storage, T instance, int maxLength) {
2492         final int newLength = storage.length + 1;
2493         final boolean arrayFull = (newLength > maxLength);
2494         T[] result = Arrays.copyOf(storage, arrayFull ? maxLength : newLength);
2495         if (newLength == 1) {
2496             result[0] = instance;
2497         } else if (arrayFull) {
2498             if (instance instanceof OutgoingSms || instance instanceof IncomingSms) {
2499                 mergeSmsOrEvictInFullStorage(result, instance);
2500             } else {
2501                 result[findItemToEvict(storage)] = instance;
2502             }
2503         } else {
2504             // insert at random place (by moving the item at the random place to the end)
2505             int insertAt = sRandom.nextInt(newLength);
2506             result[newLength - 1] = result[insertAt];
2507             result[insertAt] = instance;
2508         }
2509         return result;
2510     }
2511 
2512     /**
2513      * Merge new sms in a full storage.
2514      *
2515      * <p>If new sms is similar to old sms, merge them.
2516      * If not, merge 2 old similar sms and add the new sms.
2517      * If not, replace old sms with the lowest count.
2518      */
mergeSmsOrEvictInFullStorage(T[] storage, T instance)2519     private static <T> void mergeSmsOrEvictInFullStorage(T[] storage, T instance) {
2520         // key: hashCode, value: smsIndex
2521         SparseIntArray map = new SparseIntArray();
2522         int smsIndex1 = -1;
2523         int smsIndex2 = -1;
2524         int indexLowestCount = -1;
2525         int minCount = Integer.MAX_VALUE;
2526 
2527         for (int i = 0; i < storage.length; i++) {
2528             // If the new SMS can be merged to an existing item, merge it and return immediately.
2529             if (areSmsMergeable(storage[i], instance)) {
2530                 storage[i] = mergeSms(storage[i], instance);
2531                 return;
2532             }
2533 
2534             // Keep sms index with lowest count to evict, in case we cannot merge any 2 messages.
2535             int smsCount = getSmsCount(storage[i]);
2536             if (smsCount < minCount) {
2537                 indexLowestCount = i;
2538                 minCount = smsCount;
2539             }
2540 
2541             // Find any 2 messages in the storage that can be merged together.
2542             if (smsIndex1 != -1) {
2543                 int smsHashCode = getSmsHashCode(storage[i]);
2544                 if (map.indexOfKey(smsHashCode) < 0) {
2545                     map.append(smsHashCode, i);
2546                 } else {
2547                     smsIndex1 = map.get(smsHashCode);
2548                     smsIndex2 = i;
2549                 }
2550             }
2551         }
2552 
2553         // Merge 2 similar old sms and add the new sms
2554         if (smsIndex1 != -1) {
2555             storage[smsIndex1] = mergeSms(storage[smsIndex1], storage[smsIndex2]);
2556             storage[smsIndex2] = instance;
2557             return;
2558         }
2559 
2560         // Or replace old sms that has the lowest count
2561         storage[indexLowestCount] = instance;
2562         return;
2563     }
2564 
getSmsHashCode(T sms)2565     private static <T> int getSmsHashCode(T sms) {
2566         return sms instanceof OutgoingSms
2567                 ? ((OutgoingSms) sms).hashCode : ((IncomingSms) sms).hashCode;
2568     }
2569 
getSmsCount(T sms)2570     private static <T> int getSmsCount(T sms) {
2571         return sms instanceof OutgoingSms
2572                 ? ((OutgoingSms) sms).count : ((IncomingSms) sms).count;
2573     }
2574 
2575     /** Compares 2 SMS hash codes to check if they can be clubbed together in the metrics. */
areSmsMergeable(T instance1, T instance2)2576     private static <T> boolean areSmsMergeable(T instance1, T instance2) {
2577         return getSmsHashCode(instance1) == getSmsHashCode(instance2);
2578     }
2579 
2580     /** Merges sms2 data on top of sms1 and returns the merged value. */
mergeSms(T sms1, T sms2)2581     private static <T> T mergeSms(T sms1, T sms2) {
2582         if (sms1 instanceof OutgoingSms) {
2583             OutgoingSms tSms1 = (OutgoingSms) sms1;
2584             OutgoingSms tSms2 = (OutgoingSms) sms2;
2585             tSms1.intervalMillis = (tSms1.intervalMillis * tSms1.count
2586                     + tSms2.intervalMillis * tSms2.count) / (tSms1.count + tSms2.count);
2587             tSms1.count += tSms2.count;
2588         } else if (sms1 instanceof IncomingSms) {
2589             IncomingSms tSms1 = (IncomingSms) sms1;
2590             IncomingSms tSms2 = (IncomingSms) sms2;
2591             tSms1.count += tSms2.count;
2592         }
2593         return sms1;
2594     }
2595 
2596     /** Returns index of the item suitable for eviction when the array is full. */
findItemToEvict(T[] array)2597     private static <T> int findItemToEvict(T[] array) {
2598         if (array instanceof CellularServiceState[]) {
2599             // Evict the item that was used least recently
2600             CellularServiceState[] arr = (CellularServiceState[]) array;
2601             return IntStream.range(0, arr.length)
2602                     .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
2603                     .getAsInt();
2604         }
2605 
2606         if (array instanceof CellularDataServiceSwitch[]) {
2607             // Evict the item that was used least recently
2608             CellularDataServiceSwitch[] arr = (CellularDataServiceSwitch[]) array;
2609             return IntStream.range(0, arr.length)
2610                     .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
2611                     .getAsInt();
2612         }
2613 
2614         if (array instanceof ImsRegistrationStats[]) {
2615             // Evict the item that was used least recently
2616             ImsRegistrationStats[] arr = (ImsRegistrationStats[]) array;
2617             return IntStream.range(0, arr.length)
2618                     .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
2619                     .getAsInt();
2620         }
2621 
2622         if (array instanceof ImsRegistrationTermination[]) {
2623             // Evict the item that was used least recently
2624             ImsRegistrationTermination[] arr = (ImsRegistrationTermination[]) array;
2625             return IntStream.range(0, arr.length)
2626                     .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
2627                     .getAsInt();
2628         }
2629 
2630         if (array instanceof VoiceCallSession[]) {
2631             // For voice calls, try to keep emergency calls over regular calls.
2632             VoiceCallSession[] arr = (VoiceCallSession[]) array;
2633             int[] nonEmergencyCallIndexes = IntStream.range(0, arr.length)
2634                     .filter(i -> !arr[i].isEmergency)
2635                     .toArray();
2636             if (nonEmergencyCallIndexes.length > 0) {
2637                 return nonEmergencyCallIndexes[sRandom.nextInt(nonEmergencyCallIndexes.length)];
2638             }
2639             // If all calls in the storage are emergency calls, proceed with default case
2640             // even if the new call is not an emergency call.
2641         }
2642 
2643         return sRandom.nextInt(array.length);
2644     }
2645 
2646     /** Sanitizes the loaded array of atoms to avoid null values. */
sanitizeAtoms(T[] array, Class<T> cl)2647     private <T> T[] sanitizeAtoms(T[] array, Class<T> cl) {
2648         return ArrayUtils.emptyIfNull(array, cl);
2649     }
2650 
2651     /** Sanitizes the loaded array of atoms loaded to avoid null values and enforce max length. */
sanitizeAtoms(T[] array, Class<T> cl, int maxLength)2652     private <T> T[] sanitizeAtoms(T[] array, Class<T> cl, int maxLength) {
2653         array = sanitizeAtoms(array, cl);
2654         if (array.length > maxLength) {
2655             return Arrays.copyOf(array, maxLength);
2656         }
2657         return array;
2658     }
2659 
2660     /** Sanitizes the timestamp of the last pull loaded from persistent storage. */
sanitizeTimestamp(long timestamp)2661     private long sanitizeTimestamp(long timestamp) {
2662         return timestamp <= 0L ? getWallTimeMillis() : timestamp;
2663     }
2664 
2665     /**
2666      * Returns {@link ImsRegistrationStats} array with durations normalized to 24 hours
2667      * depending on the interval.
2668      */
normalizeData(ImsRegistrationStats[] stats, long intervalMillis)2669     private ImsRegistrationStats[] normalizeData(ImsRegistrationStats[] stats,
2670             long intervalMillis) {
2671         for (int i = 0; i < stats.length; i++) {
2672             stats[i].registeredMillis =
2673                     normalizeDurationTo24H(stats[i].registeredMillis, intervalMillis);
2674             stats[i].voiceCapableMillis =
2675                     normalizeDurationTo24H(stats[i].voiceCapableMillis, intervalMillis);
2676             stats[i].voiceAvailableMillis =
2677                     normalizeDurationTo24H(stats[i].voiceAvailableMillis, intervalMillis);
2678             stats[i].smsCapableMillis =
2679                     normalizeDurationTo24H(stats[i].smsCapableMillis, intervalMillis);
2680             stats[i].smsAvailableMillis =
2681                     normalizeDurationTo24H(stats[i].smsAvailableMillis, intervalMillis);
2682             stats[i].videoCapableMillis =
2683                     normalizeDurationTo24H(stats[i].videoCapableMillis, intervalMillis);
2684             stats[i].videoAvailableMillis =
2685                     normalizeDurationTo24H(stats[i].videoAvailableMillis, intervalMillis);
2686             stats[i].utCapableMillis =
2687                     normalizeDurationTo24H(stats[i].utCapableMillis, intervalMillis);
2688             stats[i].utAvailableMillis =
2689                     normalizeDurationTo24H(stats[i].utAvailableMillis, intervalMillis);
2690             stats[i].registeringMillis =
2691                     normalizeDurationTo24H(stats[i].registeringMillis, intervalMillis);
2692             stats[i].unregisteredMillis =
2693                     normalizeDurationTo24H(stats[i].unregisteredMillis, intervalMillis);
2694         }
2695         return stats;
2696     }
2697 
2698     /** Returns a duration normalized to 24 hours. */
normalizeDurationTo24H(long timeInMillis, long intervalMillis)2699     private long normalizeDurationTo24H(long timeInMillis, long intervalMillis) {
2700         long interval = intervalMillis < 1000 ? 1 : intervalMillis / 1000;
2701         return ((timeInMillis / 1000) * (DAY_IN_MILLIS / 1000) / interval) * 1000;
2702     }
2703 
2704     /** Returns an empty PersistAtoms with pull timestamp set to current time. */
2705     private PersistAtoms makeNewPersistAtoms() {
2706         PersistAtoms atoms = new PersistAtoms();
2707         // allow pulling only after some time so data are sufficiently aggregated
2708         long currentTime = getWallTimeMillis();
2709         atoms.buildFingerprint = Build.FINGERPRINT;
2710         atoms.voiceCallRatUsagePullTimestampMillis = currentTime;
2711         atoms.voiceCallSessionPullTimestampMillis = currentTime;
2712         atoms.incomingSmsPullTimestampMillis = currentTime;
2713         atoms.outgoingSmsPullTimestampMillis = currentTime;
2714         atoms.carrierIdTableVersion = TelephonyManager.UNKNOWN_CARRIER_ID_LIST_VERSION;
2715         atoms.dataCallSessionPullTimestampMillis = currentTime;
2716         atoms.cellularServiceStatePullTimestampMillis = currentTime;
2717         atoms.cellularDataServiceSwitchPullTimestampMillis = currentTime;
2718         atoms.imsRegistrationStatsPullTimestampMillis = currentTime;
2719         atoms.imsRegistrationTerminationPullTimestampMillis = currentTime;
2720         atoms.networkRequestsPullTimestampMillis = currentTime;
2721         atoms.networkRequestsV2PullTimestampMillis = currentTime;
2722         atoms.imsRegistrationFeatureTagStatsPullTimestampMillis = currentTime;
2723         atoms.rcsClientProvisioningStatsPullTimestampMillis = currentTime;
2724         atoms.rcsAcsProvisioningStatsPullTimestampMillis = currentTime;
2725         atoms.sipDelegateStatsPullTimestampMillis = currentTime;
2726         atoms.sipTransportFeatureTagStatsPullTimestampMillis = currentTime;
2727         atoms.sipMessageResponsePullTimestampMillis = currentTime;
2728         atoms.sipTransportSessionPullTimestampMillis = currentTime;
2729         atoms.imsDedicatedBearerListenerEventPullTimestampMillis = currentTime;
2730         atoms.imsDedicatedBearerEventPullTimestampMillis = currentTime;
2731         atoms.imsRegistrationServiceDescStatsPullTimestampMillis = currentTime;
2732         atoms.uceEventStatsPullTimestampMillis = currentTime;
2733         atoms.presenceNotifyEventPullTimestampMillis = currentTime;
2734         atoms.gbaEventPullTimestampMillis = currentTime;
2735         atoms.outgoingShortCodeSmsPullTimestampMillis = currentTime;
2736         atoms.satelliteControllerPullTimestampMillis = currentTime;
2737         atoms.satelliteSessionPullTimestampMillis = currentTime;
2738         atoms.satelliteIncomingDatagramPullTimestampMillis = currentTime;
2739         atoms.satelliteOutgoingDatagramPullTimestampMillis = currentTime;
2740         atoms.satelliteProvisionPullTimestampMillis = currentTime;
2741         atoms.satelliteSosMessageRecommenderPullTimestampMillis = currentTime;
2742         atoms.dataNetworkValidationPullTimestampMillis = currentTime;
2743         atoms.carrierRoamingSatelliteSessionPullTimestampMillis = currentTime;
2744         atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = currentTime;
2745         atoms.satelliteEntitlementPullTimestampMillis = currentTime;
2746         atoms.satelliteConfigUpdaterPullTimestampMillis = currentTime;
2747         atoms.satelliteAccessControllerPullTimestampMillis = currentTime;
2748 
2749         Rlog.d(TAG, "created new PersistAtoms");
2750         return atoms;
2751     }
2752 
2753     @VisibleForTesting
2754     protected long getWallTimeMillis() {
2755         // Epoch time in UTC, preserved across reboots, but can be adjusted e.g. by the user or NTP
2756         return System.currentTimeMillis();
2757     }
2758 }
2759