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 android.annotation.Nullable;
20 import android.content.Context;
21 import android.os.Build;
22 import android.os.Handler;
23 import android.os.HandlerThread;
24 import android.telephony.TelephonyManager;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierIdMismatch;
28 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
29 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
30 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
31 import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent;
32 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent;
33 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent;
34 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationFeatureTagStats;
35 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationServiceDescStats;
36 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats;
37 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
38 import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
39 import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequests;
40 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
41 import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
42 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
43 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
44 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
45 import com.android.internal.telephony.nano.PersistAtomsProto.SipDelegateStats;
46 import com.android.internal.telephony.nano.PersistAtomsProto.SipMessageResponse;
47 import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportFeatureTagStats;
48 import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportSession;
49 import com.android.internal.telephony.nano.PersistAtomsProto.UceEventStats;
50 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage;
51 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
52 import com.android.internal.util.ArrayUtils;
53 import com.android.telephony.Rlog;
54 
55 import java.io.FileOutputStream;
56 import java.io.IOException;
57 import java.nio.file.Files;
58 import java.nio.file.NoSuchFileException;
59 import java.security.SecureRandom;
60 import java.util.Arrays;
61 import java.util.stream.IntStream;
62 
63 /**
64  * Stores and aggregates metrics that should not be pulled at arbitrary frequency.
65  *
66  * <p>NOTE: while this class checks timestamp against {@code minIntervalMillis}, it is {@link
67  * MetricsCollector}'s responsibility to ensure {@code minIntervalMillis} is set correctly.
68  */
69 public class PersistAtomsStorage {
70     private static final String TAG = PersistAtomsStorage.class.getSimpleName();
71 
72     /** Name of the file where cached statistics are saved to. */
73     private static final String FILENAME = "persist_atoms.pb";
74 
75     /** Delay to store atoms to persistent storage to bundle multiple operations together. */
76     private static final int SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS = 30000;
77 
78     /**
79      * Delay to store atoms to persistent storage during pulls to avoid unnecessary operations.
80      *
81      * <p>This delay should be short to avoid duplicating atoms or losing pull timestamp in case of
82      * crash or power loss.
83      */
84     private static final int SAVE_TO_FILE_DELAY_FOR_GET_MILLIS = 500;
85 
86     /** Maximum number of call sessions to store between pulls. */
87     private static final int MAX_NUM_CALL_SESSIONS = 50;
88 
89     /**
90      * Maximum number of SMS to store between pulls. Incoming messages and outgoing messages are
91      * counted separately.
92      */
93     private static final int MAX_NUM_SMS = 25;
94 
95     /**
96      * Maximum number of carrier ID mismatch events stored on the device to avoid sending duplicated
97      * metrics.
98      */
99     private static final int MAX_CARRIER_ID_MISMATCH = 40;
100 
101     /** Maximum number of data call sessions to store during pulls. */
102     private static final int MAX_NUM_DATA_CALL_SESSIONS = 15;
103 
104     /** Maximum number of service states to store between pulls. */
105     private static final int MAX_NUM_CELLULAR_SERVICE_STATES = 50;
106 
107     /** Maximum number of data service switches to store between pulls. */
108     private static final int MAX_NUM_CELLULAR_DATA_SERVICE_SWITCHES = 50;
109 
110     /** Maximum number of IMS registration stats to store between pulls. */
111     private static final int MAX_NUM_IMS_REGISTRATION_STATS = 10;
112 
113     /** Maximum number of IMS registration terminations to store between pulls. */
114     private static final int MAX_NUM_IMS_REGISTRATION_TERMINATIONS = 10;
115 
116     /** Maximum number of IMS Registration Feature Tags to store between pulls. */
117     private static final int MAX_NUM_IMS_REGISTRATION_FEATURE_STATS = 25;
118 
119     /** Maximum number of RCS Client Provisioning to store between pulls. */
120     private static final int MAX_NUM_RCS_CLIENT_PROVISIONING_STATS = 10;
121 
122     /** Maximum number of RCS Acs Provisioning to store between pulls. */
123     private static final int MAX_NUM_RCS_ACS_PROVISIONING_STATS = 10;
124 
125     /** Maximum number of Sip Message Response to store between pulls. */
126     private static final int MAX_NUM_SIP_MESSAGE_RESPONSE_STATS = 25;
127 
128     /** Maximum number of Sip Transport Session to store between pulls. */
129     private static final int MAX_NUM_SIP_TRANSPORT_SESSION_STATS = 25;
130 
131     /** Maximum number of Sip Delegate to store between pulls. */
132     private static final int MAX_NUM_SIP_DELEGATE_STATS = 10;
133 
134     /** Maximum number of Sip Transport Feature Tag to store between pulls. */
135     private static final int MAX_NUM_SIP_TRANSPORT_FEATURE_TAG_STATS = 25;
136 
137     /** Maximum number of Dedicated Bearer Listener Event to store between pulls. */
138     private static final int MAX_NUM_DEDICATED_BEARER_LISTENER_EVENT_STATS = 10;
139 
140     /** Maximum number of Dedicated Bearer Event to store between pulls. */
141     private static final int MAX_NUM_DEDICATED_BEARER_EVENT_STATS = 10;
142 
143     /** Maximum number of IMS Registration Service Desc to store between pulls. */
144     private static final int MAX_NUM_IMS_REGISTRATION_SERVICE_DESC_STATS = 25;
145 
146     /** Maximum number of UCE Event to store between pulls. */
147     private static final int MAX_NUM_UCE_EVENT_STATS = 25;
148 
149     /** Maximum number of Presence Notify Event to store between pulls. */
150     private static final int MAX_NUM_PRESENCE_NOTIFY_EVENT_STATS = 50;
151 
152     /** Maximum number of GBA Event to store between pulls. */
153     private static final int MAX_NUM_GBA_EVENT_STATS = 10;
154 
155     /** Stores persist atoms and persist states of the puller. */
156     @VisibleForTesting protected final PersistAtoms mAtoms;
157 
158     /** Aggregates RAT duration and call count. */
159     private final VoiceCallRatTracker mVoiceCallRatTracker;
160 
161     /** Whether atoms should be saved immediately, skipping the delay. */
162     @VisibleForTesting protected boolean mSaveImmediately;
163 
164     private final Context mContext;
165     private final Handler mHandler;
166     private final HandlerThread mHandlerThread;
167     private static final SecureRandom sRandom = new SecureRandom();
168 
169     private Runnable mSaveRunnable =
170             new Runnable() {
171                 @Override
172                 public void run() {
173                     saveAtomsToFileNow();
174                 }
175             };
176 
PersistAtomsStorage(Context context)177     public PersistAtomsStorage(Context context) {
178         mContext = context;
179         mAtoms = loadAtomsFromFile();
180         mVoiceCallRatTracker = VoiceCallRatTracker.fromProto(mAtoms.voiceCallRatUsage);
181 
182         mHandlerThread = new HandlerThread("PersistAtomsThread");
183         mHandlerThread.start();
184         mHandler = new Handler(mHandlerThread.getLooper());
185         mSaveImmediately = false;
186     }
187 
188     /** Adds a call to the storage. */
addVoiceCallSession(VoiceCallSession call)189     public synchronized void addVoiceCallSession(VoiceCallSession call) {
190         mAtoms.voiceCallSession =
191                 insertAtRandomPlace(mAtoms.voiceCallSession, call, MAX_NUM_CALL_SESSIONS);
192         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
193 
194         Rlog.d(TAG, "Add new voice call session: " + call.toString());
195     }
196 
197     /** Adds RAT usages to the storage when a call session ends. */
addVoiceCallRatUsage(VoiceCallRatTracker ratUsages)198     public synchronized void addVoiceCallRatUsage(VoiceCallRatTracker ratUsages) {
199         mVoiceCallRatTracker.mergeWith(ratUsages);
200         mAtoms.voiceCallRatUsage = mVoiceCallRatTracker.toProto();
201         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
202     }
203 
204     /** Adds an incoming SMS to the storage. */
addIncomingSms(IncomingSms sms)205     public synchronized void addIncomingSms(IncomingSms sms) {
206         mAtoms.incomingSms = insertAtRandomPlace(mAtoms.incomingSms, sms, MAX_NUM_SMS);
207         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
208 
209         // To be removed
210         Rlog.d(TAG, "Add new incoming SMS atom: " + sms.toString());
211     }
212 
213     /** Adds an outgoing SMS to the storage. */
addOutgoingSms(OutgoingSms sms)214     public synchronized void addOutgoingSms(OutgoingSms sms) {
215         // Update the retry id, if needed, so that it's unique and larger than all
216         // previous ones. (this algorithm ignores the fact that some SMS atoms might
217         // be dropped due to limit in size of the array).
218         for (OutgoingSms storedSms : mAtoms.outgoingSms) {
219             if (storedSms.messageId == sms.messageId && storedSms.retryId >= sms.retryId) {
220                 sms.retryId = storedSms.retryId + 1;
221             }
222         }
223 
224         mAtoms.outgoingSms = insertAtRandomPlace(mAtoms.outgoingSms, sms, MAX_NUM_SMS);
225         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
226 
227         // To be removed
228         Rlog.d(TAG, "Add new outgoing SMS atom: " + sms.toString());
229     }
230 
231     /** Adds a service state to the storage, together with data service switch if any. */
addCellularServiceStateAndCellularDataServiceSwitch( CellularServiceState state, @Nullable CellularDataServiceSwitch serviceSwitch)232     public synchronized void addCellularServiceStateAndCellularDataServiceSwitch(
233             CellularServiceState state, @Nullable CellularDataServiceSwitch serviceSwitch) {
234         CellularServiceState existingState = find(state);
235         if (existingState != null) {
236             existingState.totalTimeMillis += state.totalTimeMillis;
237             existingState.lastUsedMillis = getWallTimeMillis();
238         } else {
239             state.lastUsedMillis = getWallTimeMillis();
240             mAtoms.cellularServiceState =
241                     insertAtRandomPlace(
242                             mAtoms.cellularServiceState, state, MAX_NUM_CELLULAR_SERVICE_STATES);
243         }
244 
245         if (serviceSwitch != null) {
246             CellularDataServiceSwitch existingSwitch = find(serviceSwitch);
247             if (existingSwitch != null) {
248                 existingSwitch.switchCount += serviceSwitch.switchCount;
249                 existingSwitch.lastUsedMillis = getWallTimeMillis();
250             } else {
251                 serviceSwitch.lastUsedMillis = getWallTimeMillis();
252                 mAtoms.cellularDataServiceSwitch =
253                         insertAtRandomPlace(
254                                 mAtoms.cellularDataServiceSwitch,
255                                 serviceSwitch,
256                                 MAX_NUM_CELLULAR_DATA_SERVICE_SWITCHES);
257             }
258         }
259 
260         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
261     }
262 
263     /** Adds a data call session to the storage. */
addDataCallSession(DataCallSession dataCall)264     public synchronized void addDataCallSession(DataCallSession dataCall) {
265         int index = findIndex(dataCall);
266         if (index >= 0) {
267             DataCallSession existingCall = mAtoms.dataCallSession[index];
268             dataCall.ratSwitchCount += existingCall.ratSwitchCount;
269             dataCall.durationMinutes += existingCall.durationMinutes;
270             mAtoms.dataCallSession[index] = dataCall;
271         } else {
272             mAtoms.dataCallSession =
273                     insertAtRandomPlace(
274                             mAtoms.dataCallSession, dataCall, MAX_NUM_DATA_CALL_SESSIONS);
275         }
276 
277         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
278     }
279 
280     /**
281      * Adds a new carrier ID mismatch event to the storage.
282      *
283      * @return true if the item was not present and was added to the persistent storage, false
284      *     otherwise.
285      */
addCarrierIdMismatch(CarrierIdMismatch carrierIdMismatch)286     public synchronized boolean addCarrierIdMismatch(CarrierIdMismatch carrierIdMismatch) {
287         // Check if the details of the SIM cards are already present and in case return.
288         if (find(carrierIdMismatch) != null) {
289             return false;
290         }
291         // Add the new CarrierIdMismatch at the end of the array, so that the same atom will not be
292         // sent again in future.
293         if (mAtoms.carrierIdMismatch.length == MAX_CARRIER_ID_MISMATCH) {
294             System.arraycopy(
295                     mAtoms.carrierIdMismatch,
296                     1,
297                     mAtoms.carrierIdMismatch,
298                     0,
299                     MAX_CARRIER_ID_MISMATCH - 1);
300             mAtoms.carrierIdMismatch[MAX_CARRIER_ID_MISMATCH - 1] = carrierIdMismatch;
301         } else {
302             int newLength = mAtoms.carrierIdMismatch.length + 1;
303             mAtoms.carrierIdMismatch = Arrays.copyOf(mAtoms.carrierIdMismatch, newLength);
304             mAtoms.carrierIdMismatch[newLength - 1] = carrierIdMismatch;
305         }
306         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
307         return true;
308     }
309 
310     /** Adds IMS registration stats to the storage. */
addImsRegistrationStats(ImsRegistrationStats stats)311     public synchronized void addImsRegistrationStats(ImsRegistrationStats stats) {
312         ImsRegistrationStats existingStats = find(stats);
313         if (existingStats != null) {
314             existingStats.registeredMillis += stats.registeredMillis;
315             existingStats.voiceCapableMillis += stats.voiceCapableMillis;
316             existingStats.voiceAvailableMillis += stats.voiceAvailableMillis;
317             existingStats.smsCapableMillis += stats.smsCapableMillis;
318             existingStats.smsAvailableMillis += stats.smsAvailableMillis;
319             existingStats.videoCapableMillis += stats.videoCapableMillis;
320             existingStats.videoAvailableMillis += stats.videoAvailableMillis;
321             existingStats.utCapableMillis += stats.utCapableMillis;
322             existingStats.utAvailableMillis += stats.utAvailableMillis;
323             existingStats.lastUsedMillis = getWallTimeMillis();
324         } else {
325             stats.lastUsedMillis = getWallTimeMillis();
326             mAtoms.imsRegistrationStats =
327                     insertAtRandomPlace(
328                             mAtoms.imsRegistrationStats, stats, MAX_NUM_IMS_REGISTRATION_STATS);
329         }
330         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
331     }
332 
333     /** Adds IMS registration termination to the storage. */
addImsRegistrationTermination(ImsRegistrationTermination termination)334     public synchronized void addImsRegistrationTermination(ImsRegistrationTermination termination) {
335         ImsRegistrationTermination existingTermination = find(termination);
336         if (existingTermination != null) {
337             existingTermination.count += termination.count;
338             existingTermination.lastUsedMillis = getWallTimeMillis();
339         } else {
340             termination.lastUsedMillis = getWallTimeMillis();
341             mAtoms.imsRegistrationTermination =
342                     insertAtRandomPlace(
343                             mAtoms.imsRegistrationTermination,
344                             termination,
345                             MAX_NUM_IMS_REGISTRATION_TERMINATIONS);
346         }
347         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
348     }
349 
350     /**
351      * Stores the version of the carrier ID matching table.
352      *
353      * @return true if the version is newer than last available version, false otherwise.
354      */
setCarrierIdTableVersion(int carrierIdTableVersion)355     public synchronized boolean setCarrierIdTableVersion(int carrierIdTableVersion) {
356         if (mAtoms.carrierIdTableVersion < carrierIdTableVersion) {
357             mAtoms.carrierIdTableVersion = carrierIdTableVersion;
358             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
359             return true;
360         } else {
361             return false;
362         }
363     }
364 
365     /** Adds a new {@link NetworkRequests} to the storage. */
addNetworkRequests(NetworkRequests networkRequests)366     public synchronized void addNetworkRequests(NetworkRequests networkRequests) {
367         NetworkRequests existingMetrics = find(networkRequests);
368         if (existingMetrics != null) {
369             existingMetrics.enterpriseRequestCount += networkRequests.enterpriseRequestCount;
370             existingMetrics.enterpriseReleaseCount += networkRequests.enterpriseReleaseCount;
371         } else {
372             int newLength = mAtoms.networkRequests.length + 1;
373             mAtoms.networkRequests = Arrays.copyOf(mAtoms.networkRequests, newLength);
374             mAtoms.networkRequests[newLength - 1] = networkRequests;
375         }
376         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
377     }
378 
379     /** Adds a new {@link ImsRegistrationFeatureTagStats} to the storage. */
addImsRegistrationFeatureTagStats( ImsRegistrationFeatureTagStats stats)380     public synchronized void addImsRegistrationFeatureTagStats(
381                 ImsRegistrationFeatureTagStats stats) {
382         ImsRegistrationFeatureTagStats existingStats = find(stats);
383         if (existingStats != null) {
384             existingStats.registeredMillis += stats.registeredMillis;
385         } else {
386             mAtoms.imsRegistrationFeatureTagStats =
387                 insertAtRandomPlace(mAtoms.imsRegistrationFeatureTagStats,
388                     stats, MAX_NUM_IMS_REGISTRATION_FEATURE_STATS);
389         }
390         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
391     }
392 
393     /** Adds a new {@link RcsClientProvisioningStats} to the storage. */
addRcsClientProvisioningStats(RcsClientProvisioningStats stats)394     public synchronized void addRcsClientProvisioningStats(RcsClientProvisioningStats stats) {
395         RcsClientProvisioningStats existingStats = find(stats);
396         if (existingStats != null) {
397             existingStats.count += 1;
398         } else {
399             mAtoms.rcsClientProvisioningStats =
400                 insertAtRandomPlace(mAtoms.rcsClientProvisioningStats, stats,
401                         MAX_NUM_RCS_CLIENT_PROVISIONING_STATS);
402         }
403         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
404     }
405 
406     /** Adds a new {@link RcsAcsProvisioningStats} to the storage. */
addRcsAcsProvisioningStats(RcsAcsProvisioningStats stats)407     public synchronized void addRcsAcsProvisioningStats(RcsAcsProvisioningStats stats) {
408         RcsAcsProvisioningStats existingStats = find(stats);
409         if (existingStats != null) {
410             existingStats.count += 1;
411             existingStats.stateTimerMillis += stats.stateTimerMillis;
412         } else {
413             // prevent that wrong count from caller effects total count
414             stats.count = 1;
415             mAtoms.rcsAcsProvisioningStats =
416                 insertAtRandomPlace(mAtoms.rcsAcsProvisioningStats, stats,
417                         MAX_NUM_RCS_ACS_PROVISIONING_STATS);
418         }
419         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
420     }
421 
422     /** Adds a new {@link SipDelegateStats} to the storage. */
addSipDelegateStats(SipDelegateStats stats)423     public synchronized void addSipDelegateStats(SipDelegateStats stats) {
424         mAtoms.sipDelegateStats = insertAtRandomPlace(mAtoms.sipDelegateStats, stats,
425                 MAX_NUM_SIP_DELEGATE_STATS);
426         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
427     }
428 
429     /** Adds a new {@link SipTransportFeatureTagStats} to the storage. */
addSipTransportFeatureTagStats(SipTransportFeatureTagStats stats)430     public synchronized void addSipTransportFeatureTagStats(SipTransportFeatureTagStats stats) {
431         SipTransportFeatureTagStats lastStat = find(stats);
432         if (lastStat != null) {
433             lastStat.associatedMillis += stats.associatedMillis;
434         } else {
435             mAtoms.sipTransportFeatureTagStats =
436                     insertAtRandomPlace(mAtoms.sipTransportFeatureTagStats, stats,
437                             MAX_NUM_SIP_TRANSPORT_FEATURE_TAG_STATS);
438         }
439         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
440     }
441 
442     /** Adds a new {@link SipMessageResponse} to the storage. */
addSipMessageResponse(SipMessageResponse stats)443     public synchronized void addSipMessageResponse(SipMessageResponse stats) {
444         SipMessageResponse existingStats = find(stats);
445         if (existingStats != null) {
446             existingStats.count += 1;
447         } else {
448             mAtoms.sipMessageResponse = insertAtRandomPlace(mAtoms.sipMessageResponse, stats,
449                     MAX_NUM_SIP_MESSAGE_RESPONSE_STATS);
450         }
451         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
452     }
453 
454     /** Adds a new {@link SipTransportSession} to the storage. */
addCompleteSipTransportSession(SipTransportSession stats)455     public synchronized void addCompleteSipTransportSession(SipTransportSession stats) {
456         SipTransportSession existingStats = find(stats);
457         if (existingStats != null) {
458             existingStats.sessionCount += 1;
459             if (stats.isEndedGracefully) {
460                 existingStats.endedGracefullyCount += 1;
461             }
462         } else {
463             mAtoms.sipTransportSession =
464                     insertAtRandomPlace(mAtoms.sipTransportSession, stats,
465                             MAX_NUM_SIP_TRANSPORT_SESSION_STATS);
466         }
467         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
468     }
469 
470     /** Adds a new {@link ImsDedicatedBearerListenerEvent} to the storage. */
addImsDedicatedBearerListenerEvent( ImsDedicatedBearerListenerEvent stats)471     public synchronized void addImsDedicatedBearerListenerEvent(
472                 ImsDedicatedBearerListenerEvent stats) {
473         ImsDedicatedBearerListenerEvent existingStats = find(stats);
474         if (existingStats != null) {
475             existingStats.eventCount += 1;
476         } else {
477             mAtoms.imsDedicatedBearerListenerEvent =
478                 insertAtRandomPlace(mAtoms.imsDedicatedBearerListenerEvent,
479                     stats, MAX_NUM_DEDICATED_BEARER_LISTENER_EVENT_STATS);
480         }
481         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
482     }
483 
484     /** Adds a new {@link ImsDedicatedBearerEvent} to the storage. */
addImsDedicatedBearerEvent(ImsDedicatedBearerEvent stats)485     public synchronized void addImsDedicatedBearerEvent(ImsDedicatedBearerEvent stats) {
486         ImsDedicatedBearerEvent existingStats = find(stats);
487         if (existingStats != null) {
488             existingStats.count += 1;
489         } else {
490             mAtoms.imsDedicatedBearerEvent =
491                 insertAtRandomPlace(mAtoms.imsDedicatedBearerEvent, stats,
492                         MAX_NUM_DEDICATED_BEARER_EVENT_STATS);
493         }
494         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
495     }
496 
497     /** Adds a new {@link ImsRegistrationServiceDescStats} to the storage. */
addImsRegistrationServiceDescStats( ImsRegistrationServiceDescStats stats)498     public synchronized void addImsRegistrationServiceDescStats(
499             ImsRegistrationServiceDescStats stats) {
500         ImsRegistrationServiceDescStats existingStats = find(stats);
501         if (existingStats != null) {
502             existingStats.publishedMillis += stats.publishedMillis;
503         } else {
504             mAtoms.imsRegistrationServiceDescStats =
505                 insertAtRandomPlace(mAtoms.imsRegistrationServiceDescStats,
506                     stats, MAX_NUM_IMS_REGISTRATION_SERVICE_DESC_STATS);
507         }
508         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
509     }
510 
511     /** Adds a new {@link UceEventStats} to the storage. */
addUceEventStats(UceEventStats stats)512     public synchronized void addUceEventStats(UceEventStats stats) {
513         UceEventStats existingStats = find(stats);
514         if (existingStats != null) {
515             existingStats.count += 1;
516         } else {
517             mAtoms.uceEventStats =
518                 insertAtRandomPlace(mAtoms.uceEventStats, stats, MAX_NUM_UCE_EVENT_STATS);
519         }
520         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
521     }
522 
523     /** Adds a new {@link PresenceNotifyEvent} to the storage. */
addPresenceNotifyEvent(PresenceNotifyEvent stats)524     public synchronized void addPresenceNotifyEvent(PresenceNotifyEvent stats) {
525         PresenceNotifyEvent existingStats = find(stats);
526         if (existingStats != null) {
527             existingStats.rcsCapsCount += stats.rcsCapsCount;
528             existingStats.mmtelCapsCount += stats.mmtelCapsCount;
529             existingStats.noCapsCount += stats.noCapsCount;
530             existingStats.count += stats.count;
531         } else {
532             mAtoms.presenceNotifyEvent =
533                 insertAtRandomPlace(mAtoms.presenceNotifyEvent, stats,
534                         MAX_NUM_PRESENCE_NOTIFY_EVENT_STATS);
535         }
536         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
537     }
538 
539     /** Adds a new {@link GbaEvent} to the storage. */
addGbaEvent(GbaEvent stats)540     public synchronized void addGbaEvent(GbaEvent stats) {
541         GbaEvent existingStats = find(stats);
542         if (existingStats != null) {
543             existingStats.count += 1;
544         } else {
545             mAtoms.gbaEvent =
546                 insertAtRandomPlace(mAtoms.gbaEvent, stats, MAX_NUM_GBA_EVENT_STATS);
547         }
548         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
549     }
550 
551     /**
552      * Returns and clears the voice call sessions if last pulled longer than {@code
553      * minIntervalMillis} ago, otherwise returns {@code null}.
554      */
555     @Nullable
getVoiceCallSessions(long minIntervalMillis)556     public synchronized VoiceCallSession[] getVoiceCallSessions(long minIntervalMillis) {
557         if (getWallTimeMillis() - mAtoms.voiceCallSessionPullTimestampMillis > minIntervalMillis) {
558             mAtoms.voiceCallSessionPullTimestampMillis = getWallTimeMillis();
559             VoiceCallSession[] previousCalls = mAtoms.voiceCallSession;
560             mAtoms.voiceCallSession = new VoiceCallSession[0];
561             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
562             return previousCalls;
563         } else {
564             return null;
565         }
566     }
567 
568     /**
569      * Returns and clears the voice call RAT usages if last pulled longer than {@code
570      * minIntervalMillis} ago, otherwise returns {@code null}.
571      */
572     @Nullable
getVoiceCallRatUsages(long minIntervalMillis)573     public synchronized VoiceCallRatUsage[] getVoiceCallRatUsages(long minIntervalMillis) {
574         if (getWallTimeMillis() - mAtoms.voiceCallRatUsagePullTimestampMillis > minIntervalMillis) {
575             mAtoms.voiceCallRatUsagePullTimestampMillis = getWallTimeMillis();
576             VoiceCallRatUsage[] previousUsages = mAtoms.voiceCallRatUsage;
577             mVoiceCallRatTracker.clear();
578             mAtoms.voiceCallRatUsage = new VoiceCallRatUsage[0];
579             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
580             return previousUsages;
581         } else {
582             return null;
583         }
584     }
585 
586     /**
587      * Returns and clears the incoming SMS if last pulled longer than {@code minIntervalMillis} ago,
588      * otherwise returns {@code null}.
589      */
590     @Nullable
getIncomingSms(long minIntervalMillis)591     public synchronized IncomingSms[] getIncomingSms(long minIntervalMillis) {
592         if (getWallTimeMillis() - mAtoms.incomingSmsPullTimestampMillis > minIntervalMillis) {
593             mAtoms.incomingSmsPullTimestampMillis = getWallTimeMillis();
594             IncomingSms[] previousIncomingSms = mAtoms.incomingSms;
595             mAtoms.incomingSms = new IncomingSms[0];
596             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
597             return previousIncomingSms;
598         } else {
599             return null;
600         }
601     }
602 
603     /**
604      * Returns and clears the outgoing SMS if last pulled longer than {@code minIntervalMillis} ago,
605      * otherwise returns {@code null}.
606      */
607     @Nullable
getOutgoingSms(long minIntervalMillis)608     public synchronized OutgoingSms[] getOutgoingSms(long minIntervalMillis) {
609         if (getWallTimeMillis() - mAtoms.outgoingSmsPullTimestampMillis > minIntervalMillis) {
610             mAtoms.outgoingSmsPullTimestampMillis = getWallTimeMillis();
611             OutgoingSms[] previousOutgoingSms = mAtoms.outgoingSms;
612             mAtoms.outgoingSms = new OutgoingSms[0];
613             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
614             return previousOutgoingSms;
615         } else {
616             return null;
617         }
618     }
619 
620     /**
621      * Returns and clears the data call session if last pulled longer than {@code minIntervalMillis}
622      * ago, otherwise returns {@code null}.
623      */
624     @Nullable
getDataCallSessions(long minIntervalMillis)625     public synchronized DataCallSession[] getDataCallSessions(long minIntervalMillis) {
626         if (getWallTimeMillis() - mAtoms.dataCallSessionPullTimestampMillis > minIntervalMillis) {
627             mAtoms.dataCallSessionPullTimestampMillis = getWallTimeMillis();
628             DataCallSession[] previousDataCallSession = mAtoms.dataCallSession;
629             mAtoms.dataCallSession = new DataCallSession[0];
630             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
631             return previousDataCallSession;
632         } else {
633             return null;
634         }
635     }
636 
637     /**
638      * Returns and clears the service state durations if last pulled longer than {@code
639      * minIntervalMillis} ago, otherwise returns {@code null}.
640      */
641     @Nullable
getCellularServiceStates(long minIntervalMillis)642     public synchronized CellularServiceState[] getCellularServiceStates(long minIntervalMillis) {
643         if (getWallTimeMillis() - mAtoms.cellularServiceStatePullTimestampMillis
644                 > minIntervalMillis) {
645             mAtoms.cellularServiceStatePullTimestampMillis = getWallTimeMillis();
646             CellularServiceState[] previousStates = mAtoms.cellularServiceState;
647             Arrays.stream(previousStates).forEach(state -> state.lastUsedMillis = 0L);
648             mAtoms.cellularServiceState = new CellularServiceState[0];
649             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
650             return previousStates;
651         } else {
652             return null;
653         }
654     }
655 
656     /**
657      * Returns and clears the service state durations if last pulled longer than {@code
658      * minIntervalMillis} ago, otherwise returns {@code null}.
659      */
660     @Nullable
getCellularDataServiceSwitches( long minIntervalMillis)661     public synchronized CellularDataServiceSwitch[] getCellularDataServiceSwitches(
662             long minIntervalMillis) {
663         if (getWallTimeMillis() - mAtoms.cellularDataServiceSwitchPullTimestampMillis
664                 > minIntervalMillis) {
665             mAtoms.cellularDataServiceSwitchPullTimestampMillis = getWallTimeMillis();
666             CellularDataServiceSwitch[] previousSwitches = mAtoms.cellularDataServiceSwitch;
667             Arrays.stream(previousSwitches)
668                     .forEach(serviceSwitch -> serviceSwitch.lastUsedMillis = 0L);
669             mAtoms.cellularDataServiceSwitch = new CellularDataServiceSwitch[0];
670             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
671             return previousSwitches;
672         } else {
673             return null;
674         }
675     }
676 
677     /**
678      * Returns and clears the IMS registration statistics if last pulled longer than {@code
679      * minIntervalMillis} ago, otherwise returns {@code null}.
680      */
681     @Nullable
getImsRegistrationStats(long minIntervalMillis)682     public synchronized ImsRegistrationStats[] getImsRegistrationStats(long minIntervalMillis) {
683         if (getWallTimeMillis() - mAtoms.imsRegistrationStatsPullTimestampMillis
684                 > minIntervalMillis) {
685             mAtoms.imsRegistrationStatsPullTimestampMillis = getWallTimeMillis();
686             ImsRegistrationStats[] previousStats = mAtoms.imsRegistrationStats;
687             Arrays.stream(previousStats).forEach(stats -> stats.lastUsedMillis = 0L);
688             mAtoms.imsRegistrationStats = new ImsRegistrationStats[0];
689             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
690             return previousStats;
691         } else {
692             return null;
693         }
694     }
695 
696     /**
697      * Returns and clears the IMS registration terminations if last pulled longer than {@code
698      * minIntervalMillis} ago, otherwise returns {@code null}.
699      */
700     @Nullable
getImsRegistrationTerminations( long minIntervalMillis)701     public synchronized ImsRegistrationTermination[] getImsRegistrationTerminations(
702             long minIntervalMillis) {
703         if (getWallTimeMillis() - mAtoms.imsRegistrationTerminationPullTimestampMillis
704                 > minIntervalMillis) {
705             mAtoms.imsRegistrationTerminationPullTimestampMillis = getWallTimeMillis();
706             ImsRegistrationTermination[] previousTerminations = mAtoms.imsRegistrationTermination;
707             Arrays.stream(previousTerminations)
708                     .forEach(termination -> termination.lastUsedMillis = 0L);
709             mAtoms.imsRegistrationTermination = new ImsRegistrationTermination[0];
710             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
711             return previousTerminations;
712         } else {
713             return null;
714         }
715     }
716 
717     /**
718      * Returns and clears the network requests if last pulled longer than {@code
719      * minIntervalMillis} ago, otherwise returns {@code null}.
720      */
721     @Nullable
getNetworkRequests(long minIntervalMillis)722     public synchronized NetworkRequests[] getNetworkRequests(long minIntervalMillis) {
723         if (getWallTimeMillis() - mAtoms.networkRequestsPullTimestampMillis > minIntervalMillis) {
724             mAtoms.networkRequestsPullTimestampMillis = getWallTimeMillis();
725             NetworkRequests[] previousNetworkRequests = mAtoms.networkRequests;
726             mAtoms.networkRequests = new NetworkRequests[0];
727             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
728             return previousNetworkRequests;
729         } else {
730             return null;
731         }
732     }
733 
734     /**
735      * Returns and clears the ImsRegistrationFeatureTagStats if last pulled longer than {@code
736      * minIntervalMillis} ago, otherwise returns {@code null}.
737      */
738     @Nullable
getImsRegistrationFeatureTagStats( long minIntervalMillis)739     public synchronized ImsRegistrationFeatureTagStats[] getImsRegistrationFeatureTagStats(
740             long minIntervalMillis) {
741         if (getWallTimeMillis() - mAtoms.imsRegistrationFeatureTagStatsPullTimestampMillis
742                 > minIntervalMillis) {
743             mAtoms.imsRegistrationFeatureTagStatsPullTimestampMillis = getWallTimeMillis();
744             ImsRegistrationFeatureTagStats[] previousStats =
745                     mAtoms.imsRegistrationFeatureTagStats;
746             mAtoms.imsRegistrationFeatureTagStats = new ImsRegistrationFeatureTagStats[0];
747             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
748             return previousStats;
749         } else {
750             return null;
751         }
752     }
753 
754     /**
755      * Returns and clears the RcsClientProvisioningStats if last pulled longer than {@code
756      * minIntervalMillis} ago, otherwise returns {@code null}.
757      */
758     @Nullable
getRcsClientProvisioningStats( long minIntervalMillis)759     public synchronized RcsClientProvisioningStats[] getRcsClientProvisioningStats(
760             long minIntervalMillis) {
761         if (getWallTimeMillis() - mAtoms.rcsClientProvisioningStatsPullTimestampMillis
762                 > minIntervalMillis) {
763             mAtoms.rcsClientProvisioningStatsPullTimestampMillis = getWallTimeMillis();
764             RcsClientProvisioningStats[] previousStats = mAtoms.rcsClientProvisioningStats;
765             mAtoms.rcsClientProvisioningStats = new RcsClientProvisioningStats[0];
766             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
767             return previousStats;
768         } else {
769             return null;
770         }
771     }
772 
773     /**
774      * Returns and clears the RcsAcsProvisioningStats if last pulled longer than {@code
775      * minIntervalMillis} ago, otherwise returns {@code null}.
776      */
777     @Nullable
getRcsAcsProvisioningStats( long minIntervalMillis)778     public synchronized RcsAcsProvisioningStats[] getRcsAcsProvisioningStats(
779             long minIntervalMillis) {
780         if (getWallTimeMillis() - mAtoms.rcsAcsProvisioningStatsPullTimestampMillis
781                 > minIntervalMillis) {
782             mAtoms.rcsAcsProvisioningStatsPullTimestampMillis = getWallTimeMillis();
783             RcsAcsProvisioningStats[] previousStats = mAtoms.rcsAcsProvisioningStats;
784             mAtoms.rcsAcsProvisioningStats = new RcsAcsProvisioningStats[0];
785             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
786             return previousStats;
787         } else {
788             return null;
789         }
790     }
791 
792     /**
793      * Returns and clears the SipDelegateStats if last pulled longer than {@code
794      * minIntervalMillis} ago, otherwise returns {@code null}.
795      */
796     @Nullable
getSipDelegateStats(long minIntervalMillis)797     public synchronized SipDelegateStats[] getSipDelegateStats(long minIntervalMillis) {
798         if (getWallTimeMillis() - mAtoms.sipDelegateStatsPullTimestampMillis
799                 > minIntervalMillis) {
800             mAtoms.sipDelegateStatsPullTimestampMillis = getWallTimeMillis();
801             SipDelegateStats[] previousStats = mAtoms.sipDelegateStats;
802             mAtoms.sipDelegateStats = new SipDelegateStats[0];
803             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
804             return previousStats;
805         } else {
806             return null;
807         }
808     }
809 
810     /**
811      * Returns and clears the SipTransportFeatureTagStats if last pulled longer than {@code
812      * minIntervalMillis} ago, otherwise returns {@code null}.
813      */
814     @Nullable
getSipTransportFeatureTagStats( long minIntervalMillis)815     public synchronized SipTransportFeatureTagStats[] getSipTransportFeatureTagStats(
816             long minIntervalMillis) {
817         if (getWallTimeMillis() - mAtoms.sipTransportFeatureTagStatsPullTimestampMillis
818                 > minIntervalMillis) {
819             mAtoms.sipTransportFeatureTagStatsPullTimestampMillis = getWallTimeMillis();
820             SipTransportFeatureTagStats[] previousStats = mAtoms.sipTransportFeatureTagStats;
821             mAtoms.sipTransportFeatureTagStats = new SipTransportFeatureTagStats[0];
822             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
823             return previousStats;
824         } else {
825             return null;
826         }
827     }
828 
829     /**
830      * Returns and clears the SipMessageResponse if last pulled longer than {@code
831      * minIntervalMillis} ago, otherwise returns {@code null}.
832      */
833     @Nullable
getSipMessageResponse(long minIntervalMillis)834     public synchronized SipMessageResponse[] getSipMessageResponse(long minIntervalMillis) {
835         if (getWallTimeMillis() - mAtoms.sipMessageResponsePullTimestampMillis
836                 > minIntervalMillis) {
837             mAtoms.sipMessageResponsePullTimestampMillis = getWallTimeMillis();
838             SipMessageResponse[] previousStats =
839                     mAtoms.sipMessageResponse;
840             mAtoms.sipMessageResponse = new SipMessageResponse[0];
841             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
842             return previousStats;
843         } else {
844             return null;
845         }
846     }
847 
848     /**
849      * Returns and clears the SipTransportSession if last pulled longer than {@code
850      * minIntervalMillis} ago, otherwise returns {@code null}.
851      */
852     @Nullable
getSipTransportSession(long minIntervalMillis)853     public synchronized SipTransportSession[] getSipTransportSession(long minIntervalMillis) {
854         if (getWallTimeMillis() - mAtoms.sipTransportSessionPullTimestampMillis
855                 > minIntervalMillis) {
856             mAtoms.sipTransportSessionPullTimestampMillis = getWallTimeMillis();
857             SipTransportSession[] previousStats =
858                     mAtoms.sipTransportSession;
859             mAtoms.sipTransportSession = new SipTransportSession[0];
860             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
861             return previousStats;
862         } else {
863             return null;
864         }
865     }
866 
867     /**
868      * Returns and clears the ImsDedicatedBearerListenerEvent if last pulled longer than {@code
869      * minIntervalMillis} ago, otherwise returns {@code null}.
870      */
871     @Nullable
getImsDedicatedBearerListenerEvent( long minIntervalMillis)872     public synchronized ImsDedicatedBearerListenerEvent[] getImsDedicatedBearerListenerEvent(
873             long minIntervalMillis) {
874         if (getWallTimeMillis() - mAtoms.imsDedicatedBearerListenerEventPullTimestampMillis
875                 > minIntervalMillis) {
876             mAtoms.imsDedicatedBearerListenerEventPullTimestampMillis = getWallTimeMillis();
877             ImsDedicatedBearerListenerEvent[] previousStats =
878                 mAtoms.imsDedicatedBearerListenerEvent;
879             mAtoms.imsDedicatedBearerListenerEvent = new ImsDedicatedBearerListenerEvent[0];
880             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
881             return previousStats;
882         } else {
883             return null;
884         }
885     }
886 
887     /**
888      * Returns and clears the ImsDedicatedBearerEvent if last pulled longer than {@code
889      * minIntervalMillis} ago, otherwise returns {@code null}.
890      */
891     @Nullable
getImsDedicatedBearerEvent( long minIntervalMillis)892     public synchronized ImsDedicatedBearerEvent[] getImsDedicatedBearerEvent(
893             long minIntervalMillis) {
894         if (getWallTimeMillis() - mAtoms.imsDedicatedBearerEventPullTimestampMillis
895                   > minIntervalMillis) {
896             mAtoms.imsDedicatedBearerEventPullTimestampMillis = getWallTimeMillis();
897             ImsDedicatedBearerEvent[] previousStats =
898                 mAtoms.imsDedicatedBearerEvent;
899             mAtoms.imsDedicatedBearerEvent = new ImsDedicatedBearerEvent[0];
900             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
901             return previousStats;
902         } else {
903             return null;
904         }
905     }
906 
907     /**
908      * Returns and clears the ImsRegistrationServiceDescStats if last pulled longer than {@code
909      * minIntervalMillis} ago, otherwise returns {@code null}.
910      */
911     @Nullable
getImsRegistrationServiceDescStats(long minIntervalMillis)912     public synchronized ImsRegistrationServiceDescStats[] getImsRegistrationServiceDescStats(long
913             minIntervalMillis) {
914         if (getWallTimeMillis() - mAtoms.imsRegistrationServiceDescStatsPullTimestampMillis
915                 > minIntervalMillis) {
916             mAtoms.imsRegistrationServiceDescStatsPullTimestampMillis = getWallTimeMillis();
917             ImsRegistrationServiceDescStats[] previousStats =
918                 mAtoms.imsRegistrationServiceDescStats;
919             mAtoms.imsRegistrationServiceDescStats = new ImsRegistrationServiceDescStats[0];
920             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
921             return previousStats;
922         } else {
923             return null;
924         }
925     }
926 
927     /**
928      * Returns and clears the UceEventStats if last pulled longer than {@code
929      * minIntervalMillis} ago, otherwise returns {@code null}.
930      */
931     @Nullable
getUceEventStats(long minIntervalMillis)932     public synchronized UceEventStats[] getUceEventStats(long minIntervalMillis) {
933         if (getWallTimeMillis() - mAtoms.uceEventStatsPullTimestampMillis > minIntervalMillis) {
934             mAtoms.uceEventStatsPullTimestampMillis = getWallTimeMillis();
935             UceEventStats[] previousStats = mAtoms.uceEventStats;
936             mAtoms.uceEventStats = new UceEventStats[0];
937             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
938             return previousStats;
939         } else {
940             return null;
941         }
942     }
943 
944     /**
945      * Returns and clears the PresenceNotifyEvent if last pulled longer than {@code
946      * minIntervalMillis} ago, otherwise returns {@code null}.
947      */
948     @Nullable
getPresenceNotifyEvent(long minIntervalMillis)949     public synchronized PresenceNotifyEvent[] getPresenceNotifyEvent(long minIntervalMillis) {
950         if (getWallTimeMillis() - mAtoms.presenceNotifyEventPullTimestampMillis
951                 > minIntervalMillis) {
952             mAtoms.presenceNotifyEventPullTimestampMillis = getWallTimeMillis();
953             PresenceNotifyEvent[] previousStats = mAtoms.presenceNotifyEvent;
954             mAtoms.presenceNotifyEvent = new PresenceNotifyEvent[0];
955             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
956             return previousStats;
957         } else {
958             return null;
959         }
960     }
961 
962     /**
963      * Returns and clears the GbaEvent if last pulled longer than {@code
964      * minIntervalMillis} ago, otherwise returns {@code null}.
965      */
966     @Nullable
getGbaEvent(long minIntervalMillis)967     public synchronized GbaEvent[] getGbaEvent(long minIntervalMillis) {
968         if (getWallTimeMillis() - mAtoms.gbaEventPullTimestampMillis > minIntervalMillis) {
969             mAtoms.gbaEventPullTimestampMillis = getWallTimeMillis();
970             GbaEvent[] previousStats = mAtoms.gbaEvent;
971             mAtoms.gbaEvent = new GbaEvent[0];
972             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
973             return previousStats;
974         } else {
975             return null;
976         }
977     }
978 
979     /** Loads {@link PersistAtoms} from a file in private storage. */
loadAtomsFromFile()980     private PersistAtoms loadAtomsFromFile() {
981         try {
982             PersistAtoms atoms =
983                     PersistAtoms.parseFrom(
984                             Files.readAllBytes(mContext.getFileStreamPath(FILENAME).toPath()));
985             // Start from scratch if build changes, since mixing atoms from different builds could
986             // produce strange results
987             if (!Build.FINGERPRINT.equals(atoms.buildFingerprint)) {
988                 Rlog.d(TAG, "Build changed");
989                 return makeNewPersistAtoms();
990             }
991             // check all the fields in case of situations such as OTA or crash during saving
992             atoms.voiceCallRatUsage =
993                     sanitizeAtoms(atoms.voiceCallRatUsage, VoiceCallRatUsage.class);
994             atoms.voiceCallSession =
995                     sanitizeAtoms(
996                             atoms.voiceCallSession, VoiceCallSession.class, MAX_NUM_CALL_SESSIONS);
997             atoms.incomingSms = sanitizeAtoms(atoms.incomingSms, IncomingSms.class, MAX_NUM_SMS);
998             atoms.outgoingSms = sanitizeAtoms(atoms.outgoingSms, OutgoingSms.class, MAX_NUM_SMS);
999             atoms.carrierIdMismatch =
1000                     sanitizeAtoms(
1001                             atoms.carrierIdMismatch,
1002                             CarrierIdMismatch.class,
1003                             MAX_CARRIER_ID_MISMATCH);
1004             atoms.dataCallSession =
1005                     sanitizeAtoms(
1006                             atoms.dataCallSession,
1007                             DataCallSession.class,
1008                             MAX_NUM_DATA_CALL_SESSIONS);
1009             atoms.cellularServiceState =
1010                     sanitizeAtoms(
1011                             atoms.cellularServiceState,
1012                             CellularServiceState.class,
1013                             MAX_NUM_CELLULAR_SERVICE_STATES);
1014             atoms.cellularDataServiceSwitch =
1015                     sanitizeAtoms(
1016                             atoms.cellularDataServiceSwitch,
1017                             CellularDataServiceSwitch.class,
1018                             MAX_NUM_CELLULAR_DATA_SERVICE_SWITCHES);
1019             atoms.imsRegistrationStats =
1020                     sanitizeAtoms(
1021                             atoms.imsRegistrationStats,
1022                             ImsRegistrationStats.class,
1023                             MAX_NUM_IMS_REGISTRATION_STATS);
1024             atoms.imsRegistrationTermination =
1025                     sanitizeAtoms(
1026                             atoms.imsRegistrationTermination,
1027                             ImsRegistrationTermination.class,
1028                             MAX_NUM_IMS_REGISTRATION_TERMINATIONS);
1029             atoms.networkRequests = sanitizeAtoms(atoms.networkRequests, NetworkRequests.class);
1030             atoms.imsRegistrationFeatureTagStats =
1031                     sanitizeAtoms(
1032                             atoms.imsRegistrationFeatureTagStats,
1033                             ImsRegistrationFeatureTagStats.class,
1034                             MAX_NUM_IMS_REGISTRATION_FEATURE_STATS);
1035             atoms.rcsClientProvisioningStats =
1036                     sanitizeAtoms(
1037                             atoms.rcsClientProvisioningStats,
1038                             RcsClientProvisioningStats.class,
1039                             MAX_NUM_RCS_CLIENT_PROVISIONING_STATS);
1040             atoms.rcsAcsProvisioningStats =
1041                     sanitizeAtoms(
1042                             atoms.rcsAcsProvisioningStats,
1043                             RcsAcsProvisioningStats.class,
1044                             MAX_NUM_RCS_ACS_PROVISIONING_STATS);
1045             atoms.sipDelegateStats =
1046                     sanitizeAtoms(
1047                             atoms.sipDelegateStats,
1048                             SipDelegateStats.class,
1049                             MAX_NUM_SIP_DELEGATE_STATS);
1050             atoms.sipTransportFeatureTagStats =
1051                     sanitizeAtoms(
1052                             atoms.sipTransportFeatureTagStats,
1053                             SipTransportFeatureTagStats.class,
1054                             MAX_NUM_SIP_TRANSPORT_FEATURE_TAG_STATS);
1055             atoms.sipMessageResponse =
1056                     sanitizeAtoms(
1057                             atoms.sipMessageResponse,
1058                             SipMessageResponse.class,
1059                             MAX_NUM_SIP_MESSAGE_RESPONSE_STATS);
1060             atoms.sipTransportSession =
1061                     sanitizeAtoms(
1062                             atoms.sipTransportSession,
1063                             SipTransportSession.class,
1064                             MAX_NUM_SIP_TRANSPORT_SESSION_STATS);
1065             atoms.imsDedicatedBearerListenerEvent =
1066                     sanitizeAtoms(
1067                             atoms.imsDedicatedBearerListenerEvent,
1068                             ImsDedicatedBearerListenerEvent.class,
1069                             MAX_NUM_DEDICATED_BEARER_LISTENER_EVENT_STATS);
1070             atoms.imsDedicatedBearerEvent =
1071                     sanitizeAtoms(
1072                             atoms.imsDedicatedBearerEvent,
1073                             ImsDedicatedBearerEvent.class,
1074                             MAX_NUM_DEDICATED_BEARER_EVENT_STATS);
1075             atoms.imsRegistrationServiceDescStats =
1076                     sanitizeAtoms(
1077                             atoms.imsRegistrationServiceDescStats,
1078                             ImsRegistrationServiceDescStats.class,
1079                             MAX_NUM_IMS_REGISTRATION_SERVICE_DESC_STATS);
1080             atoms.uceEventStats =
1081                     sanitizeAtoms(
1082                             atoms.uceEventStats,
1083                             UceEventStats.class,
1084                             MAX_NUM_UCE_EVENT_STATS);
1085             atoms.presenceNotifyEvent =
1086                     sanitizeAtoms(
1087                             atoms.presenceNotifyEvent,
1088                             PresenceNotifyEvent.class,
1089                             MAX_NUM_PRESENCE_NOTIFY_EVENT_STATS);
1090             atoms.gbaEvent =
1091                     sanitizeAtoms(
1092                             atoms.gbaEvent,
1093                             GbaEvent.class,
1094                             MAX_NUM_GBA_EVENT_STATS);
1095 
1096             // out of caution, sanitize also the timestamps
1097             atoms.voiceCallRatUsagePullTimestampMillis =
1098                     sanitizeTimestamp(atoms.voiceCallRatUsagePullTimestampMillis);
1099             atoms.voiceCallSessionPullTimestampMillis =
1100                     sanitizeTimestamp(atoms.voiceCallSessionPullTimestampMillis);
1101             atoms.incomingSmsPullTimestampMillis =
1102                     sanitizeTimestamp(atoms.incomingSmsPullTimestampMillis);
1103             atoms.outgoingSmsPullTimestampMillis =
1104                     sanitizeTimestamp(atoms.outgoingSmsPullTimestampMillis);
1105             atoms.dataCallSessionPullTimestampMillis =
1106                     sanitizeTimestamp(atoms.dataCallSessionPullTimestampMillis);
1107             atoms.cellularServiceStatePullTimestampMillis =
1108                     sanitizeTimestamp(atoms.cellularServiceStatePullTimestampMillis);
1109             atoms.cellularDataServiceSwitchPullTimestampMillis =
1110                     sanitizeTimestamp(atoms.cellularDataServiceSwitchPullTimestampMillis);
1111             atoms.imsRegistrationStatsPullTimestampMillis =
1112                     sanitizeTimestamp(atoms.imsRegistrationStatsPullTimestampMillis);
1113             atoms.imsRegistrationTerminationPullTimestampMillis =
1114                     sanitizeTimestamp(atoms.imsRegistrationTerminationPullTimestampMillis);
1115             atoms.networkRequestsPullTimestampMillis =
1116                     sanitizeTimestamp(atoms.networkRequestsPullTimestampMillis);
1117             atoms.imsRegistrationFeatureTagStatsPullTimestampMillis =
1118                     sanitizeTimestamp(atoms.imsRegistrationFeatureTagStatsPullTimestampMillis);
1119             atoms.rcsClientProvisioningStatsPullTimestampMillis =
1120                     sanitizeTimestamp(atoms.rcsClientProvisioningStatsPullTimestampMillis);
1121             atoms.rcsAcsProvisioningStatsPullTimestampMillis =
1122                     sanitizeTimestamp(atoms.rcsAcsProvisioningStatsPullTimestampMillis);
1123             atoms.sipDelegateStatsPullTimestampMillis =
1124                     sanitizeTimestamp(atoms.sipDelegateStatsPullTimestampMillis);
1125             atoms.sipTransportFeatureTagStatsPullTimestampMillis =
1126                     sanitizeTimestamp(atoms.sipTransportFeatureTagStatsPullTimestampMillis);
1127             atoms.sipMessageResponsePullTimestampMillis =
1128                     sanitizeTimestamp(atoms.sipMessageResponsePullTimestampMillis);
1129             atoms.sipTransportSessionPullTimestampMillis =
1130                     sanitizeTimestamp(atoms.sipTransportSessionPullTimestampMillis);
1131             atoms.imsDedicatedBearerListenerEventPullTimestampMillis =
1132                     sanitizeTimestamp(atoms.imsDedicatedBearerListenerEventPullTimestampMillis);
1133             atoms.imsDedicatedBearerEventPullTimestampMillis =
1134                     sanitizeTimestamp(atoms.imsDedicatedBearerEventPullTimestampMillis);
1135             atoms.imsRegistrationServiceDescStatsPullTimestampMillis =
1136                     sanitizeTimestamp(atoms.imsRegistrationServiceDescStatsPullTimestampMillis);
1137             atoms.uceEventStatsPullTimestampMillis =
1138                     sanitizeTimestamp(atoms.uceEventStatsPullTimestampMillis);
1139             atoms.presenceNotifyEventPullTimestampMillis =
1140                     sanitizeTimestamp(atoms.presenceNotifyEventPullTimestampMillis);
1141             atoms.gbaEventPullTimestampMillis =
1142                     sanitizeTimestamp(atoms.gbaEventPullTimestampMillis);
1143 
1144             return atoms;
1145         } catch (NoSuchFileException e) {
1146             Rlog.d(TAG, "PersistAtoms file not found");
1147         } catch (IOException | NullPointerException e) {
1148             Rlog.e(TAG, "cannot load/parse PersistAtoms", e);
1149         }
1150         return makeNewPersistAtoms();
1151     }
1152 
1153     /**
1154      * Posts message to save a copy of {@link PersistAtoms} to a file after a delay.
1155      *
1156      * <p>The delay is introduced to avoid too frequent operations to disk, which would negatively
1157      * impact the power consumption.
1158      */
saveAtomsToFile(int delayMillis)1159     private void saveAtomsToFile(int delayMillis) {
1160         if (delayMillis > 0 && !mSaveImmediately) {
1161             mHandler.removeCallbacks(mSaveRunnable);
1162             if (mHandler.postDelayed(mSaveRunnable, delayMillis)) {
1163                 return;
1164             }
1165         }
1166         // In case of error posting the event or if delay is 0, save immediately
1167         saveAtomsToFileNow();
1168     }
1169 
1170     /** Saves a copy of {@link PersistAtoms} to a file in private storage. */
saveAtomsToFileNow()1171     private synchronized void saveAtomsToFileNow() {
1172         try (FileOutputStream stream = mContext.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
1173             stream.write(PersistAtoms.toByteArray(mAtoms));
1174         } catch (IOException e) {
1175             Rlog.e(TAG, "cannot save PersistAtoms", e);
1176         }
1177     }
1178 
1179     /**
1180      * Returns the service state that has the same dimension values with the given one, or {@code
1181      * null} if it does not exist.
1182      */
find(CellularServiceState key)1183     private @Nullable CellularServiceState find(CellularServiceState key) {
1184         for (CellularServiceState state : mAtoms.cellularServiceState) {
1185             if (state.voiceRat == key.voiceRat
1186                     && state.dataRat == key.dataRat
1187                     && state.voiceRoamingType == key.voiceRoamingType
1188                     && state.dataRoamingType == key.dataRoamingType
1189                     && state.isEndc == key.isEndc
1190                     && state.simSlotIndex == key.simSlotIndex
1191                     && state.isMultiSim == key.isMultiSim
1192                     && state.carrierId == key.carrierId) {
1193                 return state;
1194             }
1195         }
1196         return null;
1197     }
1198 
1199     /**
1200      * Returns the data service switch that has the same dimension values with the given one, or
1201      * {@code null} if it does not exist.
1202      */
find(CellularDataServiceSwitch key)1203     private @Nullable CellularDataServiceSwitch find(CellularDataServiceSwitch key) {
1204         for (CellularDataServiceSwitch serviceSwitch : mAtoms.cellularDataServiceSwitch) {
1205             if (serviceSwitch.ratFrom == key.ratFrom
1206                     && serviceSwitch.ratTo == key.ratTo
1207                     && serviceSwitch.simSlotIndex == key.simSlotIndex
1208                     && serviceSwitch.isMultiSim == key.isMultiSim
1209                     && serviceSwitch.carrierId == key.carrierId) {
1210                 return serviceSwitch;
1211             }
1212         }
1213         return null;
1214     }
1215 
1216     /**
1217      * Returns the carrier ID mismatch event that has the same dimension values with the given one,
1218      * or {@code null} if it does not exist.
1219      */
find(CarrierIdMismatch key)1220     private @Nullable CarrierIdMismatch find(CarrierIdMismatch key) {
1221         for (CarrierIdMismatch mismatch : mAtoms.carrierIdMismatch) {
1222             if (mismatch.mccMnc.equals(key.mccMnc)
1223                     && mismatch.gid1.equals(key.gid1)
1224                     && mismatch.spn.equals(key.spn)
1225                     && mismatch.pnn.equals(key.pnn)) {
1226                 return mismatch;
1227             }
1228         }
1229         return null;
1230     }
1231 
1232     /**
1233      * Returns the IMS registration stats that has the same dimension values with the given one, or
1234      * {@code null} if it does not exist.
1235      */
find(ImsRegistrationStats key)1236     private @Nullable ImsRegistrationStats find(ImsRegistrationStats key) {
1237         for (ImsRegistrationStats stats : mAtoms.imsRegistrationStats) {
1238             if (stats.carrierId == key.carrierId
1239                     && stats.simSlotIndex == key.simSlotIndex
1240                     && stats.rat == key.rat) {
1241                 return stats;
1242             }
1243         }
1244         return null;
1245     }
1246 
1247     /**
1248      * Returns the IMS registration termination that has the same dimension values with the given
1249      * one, or {@code null} if it does not exist.
1250      */
find(ImsRegistrationTermination key)1251     private @Nullable ImsRegistrationTermination find(ImsRegistrationTermination key) {
1252         for (ImsRegistrationTermination termination : mAtoms.imsRegistrationTermination) {
1253             if (termination.carrierId == key.carrierId
1254                     && termination.isMultiSim == key.isMultiSim
1255                     && termination.ratAtEnd == key.ratAtEnd
1256                     && termination.setupFailed == key.setupFailed
1257                     && termination.reasonCode == key.reasonCode
1258                     && termination.extraCode == key.extraCode
1259                     && termination.extraMessage.equals(key.extraMessage)) {
1260                 return termination;
1261             }
1262         }
1263         return null;
1264     }
1265 
1266     /**
1267      * Returns the network requests event that has the same carrier id as the given one,
1268      * or {@code null} if it does not exist.
1269      */
find(NetworkRequests key)1270     private @Nullable NetworkRequests find(NetworkRequests key) {
1271         for (NetworkRequests item : mAtoms.networkRequests) {
1272             if (item.carrierId == key.carrierId) {
1273                 return item;
1274             }
1275         }
1276         return null;
1277     }
1278 
1279     /**
1280      * Returns the index of data call session that has the same random dimension as the given one,
1281      * or -1 if it does not exist.
1282      */
findIndex(DataCallSession key)1283     private int findIndex(DataCallSession key) {
1284         for (int i = 0; i < mAtoms.dataCallSession.length; i++) {
1285             if (mAtoms.dataCallSession[i].dimension == key.dimension) {
1286                 return i;
1287             }
1288         }
1289         return -1;
1290     }
1291     /**
1292      * Returns the Dedicated Bearer Listener event that has the same carrier id, slot id, rat, qci
1293      * and established state as the given one, or {@code null} if it does not exist.
1294      */
find(ImsDedicatedBearerListenerEvent key)1295     private @Nullable ImsDedicatedBearerListenerEvent find(ImsDedicatedBearerListenerEvent key) {
1296         for (ImsDedicatedBearerListenerEvent stats : mAtoms.imsDedicatedBearerListenerEvent) {
1297             if (stats.carrierId == key.carrierId
1298                     && stats.slotId == key.slotId
1299                     && stats.ratAtEnd == key.ratAtEnd
1300                     && stats.qci == key.qci
1301                     && stats.dedicatedBearerEstablished == key.dedicatedBearerEstablished) {
1302                 return stats;
1303             }
1304         }
1305         return null;
1306     }
1307 
1308     /**
1309      * Returns the Dedicated Bearer event that has the same carrier id, slot id, rat,
1310      * qci, bearer state, local/remote connection and exsting listener as the given one,
1311      * or {@code null} if it does not exist.
1312      */
find(ImsDedicatedBearerEvent key)1313     private @Nullable ImsDedicatedBearerEvent find(ImsDedicatedBearerEvent key) {
1314         for (ImsDedicatedBearerEvent stats : mAtoms.imsDedicatedBearerEvent) {
1315             if (stats.carrierId == key.carrierId
1316                     && stats.slotId == key.slotId
1317                     && stats.ratAtEnd == key.ratAtEnd
1318                     && stats.qci == key.qci
1319                     && stats.bearerState == key.bearerState
1320                     && stats.localConnectionInfoReceived == key.localConnectionInfoReceived
1321                     && stats.remoteConnectionInfoReceived == key.remoteConnectionInfoReceived
1322                     && stats.hasListeners == key.hasListeners) {
1323                 return stats;
1324             }
1325         }
1326         return null;
1327     }
1328 
1329     /**
1330      * Returns the Registration Feature Tag that has the same carrier id, slot id,
1331      * feature tag name or custom feature tag name and registration tech as the given one,
1332      * or {@code null} if it does not exist.
1333      */
find(ImsRegistrationFeatureTagStats key)1334     private @Nullable ImsRegistrationFeatureTagStats find(ImsRegistrationFeatureTagStats key) {
1335         for (ImsRegistrationFeatureTagStats stats : mAtoms.imsRegistrationFeatureTagStats) {
1336             if (stats.carrierId == key.carrierId
1337                     && stats.slotId == key.slotId
1338                     && stats.featureTagName == key.featureTagName
1339                     && stats.registrationTech == key.registrationTech) {
1340                 return stats;
1341             }
1342         }
1343         return null;
1344     }
1345 
1346     /**
1347      * Returns Client Provisioning that has the same carrier id, slot id and event as the given
1348      * one, or {@code null} if it does not exist.
1349      */
find(RcsClientProvisioningStats key)1350     private @Nullable RcsClientProvisioningStats find(RcsClientProvisioningStats key) {
1351         for (RcsClientProvisioningStats stats : mAtoms.rcsClientProvisioningStats) {
1352             if (stats.carrierId == key.carrierId
1353                     && stats.slotId == key.slotId
1354                     && stats.event == key.event) {
1355                 return stats;
1356             }
1357         }
1358         return null;
1359     }
1360 
1361     /**
1362      * Returns ACS Provisioning that has the same carrier id, slot id, response code, response type
1363      * and SR supported as the given one, or {@code null} if it does not exist.
1364      */
find(RcsAcsProvisioningStats key)1365     private @Nullable RcsAcsProvisioningStats find(RcsAcsProvisioningStats key) {
1366         for (RcsAcsProvisioningStats stats : mAtoms.rcsAcsProvisioningStats) {
1367             if (stats.carrierId == key.carrierId
1368                     && stats.slotId == key.slotId
1369                     && stats.responseCode == key.responseCode
1370                     && stats.responseType == key.responseType
1371                     && stats.isSingleRegistrationEnabled == key.isSingleRegistrationEnabled) {
1372                 return stats;
1373             }
1374         }
1375         return null;
1376     }
1377 
1378     /**
1379      * Returns Sip Message Response that has the same carrier id, slot id, method, response,
1380      * direction and error as the given one, or {@code null} if it does not exist.
1381      */
find(SipMessageResponse key)1382     private @Nullable SipMessageResponse find(SipMessageResponse key) {
1383         for (SipMessageResponse stats : mAtoms.sipMessageResponse) {
1384             if (stats.carrierId == key.carrierId
1385                     && stats.slotId == key.slotId
1386                     && stats.sipMessageMethod == key.sipMessageMethod
1387                     && stats.sipMessageResponse == key.sipMessageResponse
1388                     && stats.sipMessageDirection == key.sipMessageDirection
1389                     && stats.messageError == key.messageError) {
1390                 return stats;
1391             }
1392         }
1393         return null;
1394     }
1395 
1396     /**
1397      * Returns Sip Transport Session that has the same carrier id, slot id, method, direction and
1398      * response as the given one, or {@code null} if it does not exist.
1399      */
find(SipTransportSession key)1400     private @Nullable SipTransportSession find(SipTransportSession key) {
1401         for (SipTransportSession stats : mAtoms.sipTransportSession) {
1402             if (stats.carrierId == key.carrierId
1403                     && stats.slotId == key.slotId
1404                     && stats.sessionMethod == key.sessionMethod
1405                     && stats.sipMessageDirection == key.sipMessageDirection
1406                     && stats.sipResponse == key.sipResponse) {
1407                 return stats;
1408             }
1409         }
1410         return null;
1411     }
1412 
1413     /**
1414      * Returns Registration Service Desc Stats that has the same carrier id, slot id, service id or
1415      * custom service id, service id version and registration tech as the given one,
1416      * or {@code null} if it does not exist.
1417      */
find(ImsRegistrationServiceDescStats key)1418     private @Nullable ImsRegistrationServiceDescStats find(ImsRegistrationServiceDescStats key) {
1419         for (ImsRegistrationServiceDescStats stats : mAtoms.imsRegistrationServiceDescStats) {
1420             if (stats.carrierId == key.carrierId
1421                     && stats.slotId == key.slotId
1422                     && stats.serviceIdName == key.serviceIdName
1423                     && stats.serviceIdVersion == key.serviceIdVersion
1424                     && stats.registrationTech == key.registrationTech) {
1425                 return stats;
1426             }
1427         }
1428         return null;
1429     }
1430 
1431     /**
1432      * Returns UCE Event Stats that has the same carrier id, slot id, event result, command code and
1433      * network response as the given one, or {@code null} if it does not exist.
1434      */
find(UceEventStats key)1435     private @Nullable UceEventStats find(UceEventStats key) {
1436         for (UceEventStats stats : mAtoms.uceEventStats) {
1437             if (stats.carrierId == key.carrierId
1438                     && stats.slotId == key.slotId
1439                     && stats.type == key.type
1440                     && stats.successful == key.successful
1441                     && stats.commandCode == key.commandCode
1442                     && stats.networkResponse == key.networkResponse) {
1443                 return stats;
1444             }
1445         }
1446         return null;
1447     }
1448 
1449     /**
1450      * Returns Presence Notify Event that has the same carrier id, slot id, reason and body in
1451      * response as the given one, or {@code null} if it does not exist.
1452      */
find(PresenceNotifyEvent key)1453     private @Nullable PresenceNotifyEvent find(PresenceNotifyEvent key) {
1454         for (PresenceNotifyEvent stats : mAtoms.presenceNotifyEvent) {
1455             if (stats.carrierId == key.carrierId
1456                     && stats.slotId == key.slotId
1457                     && stats.reason == key.reason
1458                     && stats.contentBodyReceived == key.contentBodyReceived) {
1459                 return stats;
1460             }
1461         }
1462         return null;
1463     }
1464 
1465     /**
1466      * Returns GBA Event that has the same carrier id, slot id, result of operation and fail reason
1467      * as the given one, or {@code null} if it does not exist.
1468      */
find(GbaEvent key)1469     private @Nullable GbaEvent find(GbaEvent key) {
1470         for (GbaEvent stats : mAtoms.gbaEvent) {
1471             if (stats.carrierId == key.carrierId
1472                     && stats.slotId == key.slotId
1473                     && stats.successful == key.successful
1474                     && stats.failedReason == key.failedReason) {
1475                 return stats;
1476             }
1477         }
1478         return null;
1479     }
1480 
1481     /**
1482      * Returns Sip Transport Feature Tag Stats that has the same carrier id, slot id, feature tag
1483      * name, deregister reason, denied reason and feature tag name or custom feature tag name as
1484      * the given one, or {@code null} if it does not exist.
1485      */
find(SipTransportFeatureTagStats key)1486     private @Nullable SipTransportFeatureTagStats find(SipTransportFeatureTagStats key) {
1487         for (SipTransportFeatureTagStats stat: mAtoms.sipTransportFeatureTagStats) {
1488             if (stat.carrierId == key.carrierId
1489                     && stat.slotId == key.slotId
1490                     && stat.featureTagName == key.featureTagName
1491                     && stat.sipTransportDeregisteredReason == key.sipTransportDeregisteredReason
1492                     && stat.sipTransportDeniedReason == key.sipTransportDeniedReason) {
1493                 return stat;
1494             }
1495         }
1496         return null;
1497     }
1498 
1499     /**
1500      * Inserts a new element in a random position in an array with a maximum size, replacing the
1501      * least recent item if possible.
1502      */
insertAtRandomPlace(T[] storage, T instance, int maxLength)1503     private static <T> T[] insertAtRandomPlace(T[] storage, T instance, int maxLength) {
1504         final int newLength = storage.length + 1;
1505         final boolean arrayFull = (newLength > maxLength);
1506         T[] result = Arrays.copyOf(storage, arrayFull ? maxLength : newLength);
1507         if (newLength == 1) {
1508             result[0] = instance;
1509         } else if (arrayFull) {
1510             result[findItemToEvict(storage)] = instance;
1511         } else {
1512             // insert at random place (by moving the item at the random place to the end)
1513             int insertAt = sRandom.nextInt(newLength);
1514             result[newLength - 1] = result[insertAt];
1515             result[insertAt] = instance;
1516         }
1517         return result;
1518     }
1519 
1520     /** Returns index of the item suitable for eviction when the array is full. */
findItemToEvict(T[] array)1521     private static <T> int findItemToEvict(T[] array) {
1522         if (array instanceof CellularServiceState[]) {
1523             CellularServiceState[] arr = (CellularServiceState[]) array;
1524             return IntStream.range(0, arr.length)
1525                     .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
1526                     .getAsInt();
1527         }
1528 
1529         if (array instanceof CellularDataServiceSwitch[]) {
1530             CellularDataServiceSwitch[] arr = (CellularDataServiceSwitch[]) array;
1531             return IntStream.range(0, arr.length)
1532                     .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
1533                     .getAsInt();
1534         }
1535 
1536         if (array instanceof ImsRegistrationStats[]) {
1537             ImsRegistrationStats[] arr = (ImsRegistrationStats[]) array;
1538             return IntStream.range(0, arr.length)
1539                     .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
1540                     .getAsInt();
1541         }
1542 
1543         if (array instanceof ImsRegistrationTermination[]) {
1544             ImsRegistrationTermination[] arr = (ImsRegistrationTermination[]) array;
1545             return IntStream.range(0, arr.length)
1546                     .reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
1547                     .getAsInt();
1548         }
1549 
1550         return sRandom.nextInt(array.length);
1551     }
1552 
1553     /** Sanitizes the loaded array of atoms to avoid null values. */
sanitizeAtoms(T[] array, Class<T> cl)1554     private <T> T[] sanitizeAtoms(T[] array, Class<T> cl) {
1555         return ArrayUtils.emptyIfNull(array, cl);
1556     }
1557 
1558     /** Sanitizes the loaded array of atoms loaded to avoid null values and enforce max length. */
sanitizeAtoms(T[] array, Class<T> cl, int maxLength)1559     private <T> T[] sanitizeAtoms(T[] array, Class<T> cl, int maxLength) {
1560         array = sanitizeAtoms(array, cl);
1561         if (array.length > maxLength) {
1562             return Arrays.copyOf(array, maxLength);
1563         }
1564         return array;
1565     }
1566 
1567     /** Sanitizes the timestamp of the last pull loaded from persistent storage. */
sanitizeTimestamp(long timestamp)1568     private long sanitizeTimestamp(long timestamp) {
1569         return timestamp <= 0L ? getWallTimeMillis() : timestamp;
1570     }
1571 
1572     /** Returns an empty PersistAtoms with pull timestamp set to current time. */
makeNewPersistAtoms()1573     private PersistAtoms makeNewPersistAtoms() {
1574         PersistAtoms atoms = new PersistAtoms();
1575         // allow pulling only after some time so data are sufficiently aggregated
1576         long currentTime = getWallTimeMillis();
1577         atoms.buildFingerprint = Build.FINGERPRINT;
1578         atoms.voiceCallRatUsagePullTimestampMillis = currentTime;
1579         atoms.voiceCallSessionPullTimestampMillis = currentTime;
1580         atoms.incomingSmsPullTimestampMillis = currentTime;
1581         atoms.outgoingSmsPullTimestampMillis = currentTime;
1582         atoms.carrierIdTableVersion = TelephonyManager.UNKNOWN_CARRIER_ID_LIST_VERSION;
1583         atoms.dataCallSessionPullTimestampMillis = currentTime;
1584         atoms.cellularServiceStatePullTimestampMillis = currentTime;
1585         atoms.cellularDataServiceSwitchPullTimestampMillis = currentTime;
1586         atoms.imsRegistrationStatsPullTimestampMillis = currentTime;
1587         atoms.imsRegistrationTerminationPullTimestampMillis = currentTime;
1588         atoms.networkRequestsPullTimestampMillis = currentTime;
1589         atoms.imsRegistrationFeatureTagStatsPullTimestampMillis = currentTime;
1590         atoms.rcsClientProvisioningStatsPullTimestampMillis = currentTime;
1591         atoms.rcsAcsProvisioningStatsPullTimestampMillis = currentTime;
1592         atoms.sipDelegateStatsPullTimestampMillis = currentTime;
1593         atoms.sipTransportFeatureTagStatsPullTimestampMillis = currentTime;
1594         atoms.sipMessageResponsePullTimestampMillis = currentTime;
1595         atoms.sipTransportSessionPullTimestampMillis = currentTime;
1596         atoms.imsDedicatedBearerListenerEventPullTimestampMillis = currentTime;
1597         atoms.imsDedicatedBearerEventPullTimestampMillis = currentTime;
1598         atoms.imsRegistrationServiceDescStatsPullTimestampMillis = currentTime;
1599         atoms.uceEventStatsPullTimestampMillis = currentTime;
1600         atoms.presenceNotifyEventPullTimestampMillis = currentTime;
1601         atoms.gbaEventPullTimestampMillis = currentTime;
1602 
1603         Rlog.d(TAG, "created new PersistAtoms");
1604         return atoms;
1605     }
1606 
1607     @VisibleForTesting
getWallTimeMillis()1608     protected long getWallTimeMillis() {
1609         // Epoch time in UTC, preserved across reboots, but can be adjusted e.g. by the user or NTP
1610         return System.currentTimeMillis();
1611     }
1612 }
1613