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