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