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