blob: 000d1e6648b22e6f7acff79c4ceeed6094eca949 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony.metrics;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyManager.NetworkTypeBitMask;
import android.util.SparseIntArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.nano.PersistAtomsProto.CarrierIdMismatch;
import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent;
import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent;
import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent;
import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationFeatureTagStats;
import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationServiceDescStats;
import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats;
import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequestsV2;
import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
import com.android.internal.telephony.nano.PersistAtomsProto.SipDelegateStats;
import com.android.internal.telephony.nano.PersistAtomsProto.SipMessageResponse;
import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportFeatureTagStats;
import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportSession;
import com.android.internal.telephony.nano.PersistAtomsProto.UceEventStats;
import com.android.internal.telephony.nano.PersistAtomsProto.UnmeteredNetworks;
import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage;
import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
import com.android.internal.util.ArrayUtils;
import com.android.telephony.Rlog;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.stream.IntStream;
/**
* Stores and aggregates metrics that should not be pulled at arbitrary frequency.
*
* <p>NOTE: while this class checks timestamp against {@code minIntervalMillis}, it is {@link
* MetricsCollector}'s responsibility to ensure {@code minIntervalMillis} is set correctly.
*/
public class PersistAtomsStorage {
private static final String TAG = PersistAtomsStorage.class.getSimpleName();
/** Name of the file where cached statistics are saved to. */
private static final String FILENAME = "persist_atoms.pb";
/** Delay to store atoms to persistent storage to bundle multiple operations together. */
private static final int SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS = 30000;
/**
* Delay to store atoms to persistent storage during pulls to avoid unnecessary operations.
*
* <p>This delay should be short to avoid duplicating atoms or losing pull timestamp in case of
* crash or power loss.
*/
private static final int SAVE_TO_FILE_DELAY_FOR_GET_MILLIS = 500;
/** Maximum number of call sessions to store between pulls. */
private final int mMaxNumVoiceCallSessions;
/**
* Maximum number of SMS to store between pulls. Incoming messages and outgoing messages are
* counted separately.
*/
private final int mMaxNumSms;
/**
* Maximum number of carrier ID mismatch events stored on the device to avoid sending duplicated
* metrics.
*/
private final int mMaxNumCarrierIdMismatches;
/** Maximum number of data call sessions to store during pulls. */
private final int mMaxNumDataCallSessions;
/** Maximum number of service states to store between pulls. */
private final int mMaxNumCellularServiceStates;
/** Maximum number of data service switches to store between pulls. */
private final int mMaxNumCellularDataSwitches;
/** Maximum number of IMS registration stats to store between pulls. */
private final int mMaxNumImsRegistrationStats;
/** Maximum number of IMS registration terminations to store between pulls. */
private final int mMaxNumImsRegistrationTerminations;
/** Maximum number of IMS Registration Feature Tags to store between pulls. */
private final int mMaxNumImsRegistrationFeatureStats;
/** Maximum number of RCS Client Provisioning to store between pulls. */
private final int mMaxNumRcsClientProvisioningStats;
/** Maximum number of RCS Acs Provisioning to store between pulls. */
private final int mMaxNumRcsAcsProvisioningStats;
/** Maximum number of Sip Message Response to store between pulls. */
private final int mMaxNumSipMessageResponseStats;
/** Maximum number of Sip Transport Session to store between pulls. */
private final int mMaxNumSipTransportSessionStats;
/** Maximum number of Sip Delegate to store between pulls. */
private final int mMaxNumSipDelegateStats;
/** Maximum number of Sip Transport Feature Tag to store between pulls. */
private final int mMaxNumSipTransportFeatureTagStats;
/** Maximum number of Dedicated Bearer Listener Event to store between pulls. */
private final int mMaxNumDedicatedBearerListenerEventStats;
/** Maximum number of Dedicated Bearer Event to store between pulls. */
private final int mMaxNumDedicatedBearerEventStats;
/** Maximum number of IMS Registration Service Desc to store between pulls. */
private final int mMaxNumImsRegistrationServiceDescStats;
/** Maximum number of UCE Event to store between pulls. */
private final int mMaxNumUceEventStats;
/** Maximum number of Presence Notify Event to store between pulls. */
private final int mMaxNumPresenceNotifyEventStats;
/** Maximum number of GBA Event to store between pulls. */
private final int mMaxNumGbaEventStats;
/** Stores persist atoms and persist states of the puller. */
@VisibleForTesting protected PersistAtoms mAtoms;
/** Aggregates RAT duration and call count. */
private final VoiceCallRatTracker mVoiceCallRatTracker;
/** Whether atoms should be saved immediately, skipping the delay. */
@VisibleForTesting protected boolean mSaveImmediately;
private final Context mContext;
private final Handler mHandler;
private final HandlerThread mHandlerThread;
private static final SecureRandom sRandom = new SecureRandom();
private Runnable mSaveRunnable =
new Runnable() {
@Override
public void run() {
saveAtomsToFileNow();
}
};
public PersistAtomsStorage(Context context) {
mContext = context;
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_RAM_LOW)) {
Rlog.i(TAG, "Low RAM device");
mMaxNumVoiceCallSessions = 10;
mMaxNumSms = 5;
mMaxNumCarrierIdMismatches = 8;
mMaxNumDataCallSessions = 5;
mMaxNumCellularServiceStates = 10;
mMaxNumCellularDataSwitches = 5;
mMaxNumImsRegistrationStats = 5;
mMaxNumImsRegistrationTerminations = 5;
mMaxNumImsRegistrationFeatureStats = 15;
mMaxNumRcsClientProvisioningStats = 5;
mMaxNumRcsAcsProvisioningStats = 5;
mMaxNumSipMessageResponseStats = 10;
mMaxNumSipTransportSessionStats = 10;
mMaxNumSipDelegateStats = 5;
mMaxNumSipTransportFeatureTagStats = 15;
mMaxNumDedicatedBearerListenerEventStats = 5;
mMaxNumDedicatedBearerEventStats = 5;
mMaxNumImsRegistrationServiceDescStats = 15;
mMaxNumUceEventStats = 5;
mMaxNumPresenceNotifyEventStats = 10;
mMaxNumGbaEventStats = 5;
} else {
mMaxNumVoiceCallSessions = 50;
mMaxNumSms = 25;
mMaxNumCarrierIdMismatches = 40;
mMaxNumDataCallSessions = 15;
mMaxNumCellularServiceStates = 50;
mMaxNumCellularDataSwitches = 50;
mMaxNumImsRegistrationStats = 10;
mMaxNumImsRegistrationTerminations = 10;
mMaxNumImsRegistrationFeatureStats = 25;
mMaxNumRcsClientProvisioningStats = 10;
mMaxNumRcsAcsProvisioningStats = 10;
mMaxNumSipMessageResponseStats = 25;
mMaxNumSipTransportSessionStats = 25;
mMaxNumSipDelegateStats = 10;
mMaxNumSipTransportFeatureTagStats = 25;
mMaxNumDedicatedBearerListenerEventStats = 10;
mMaxNumDedicatedBearerEventStats = 10;
mMaxNumImsRegistrationServiceDescStats = 25;
mMaxNumUceEventStats = 25;
mMaxNumPresenceNotifyEventStats = 50;
mMaxNumGbaEventStats = 10;
}
mAtoms = loadAtomsFromFile();
mVoiceCallRatTracker = VoiceCallRatTracker.fromProto(mAtoms.voiceCallRatUsage);
mHandlerThread = new HandlerThread("PersistAtomsThread");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mSaveImmediately = false;
}
/** Adds a call to the storage. */
public synchronized void addVoiceCallSession(VoiceCallSession call) {
mAtoms.voiceCallSession =
insertAtRandomPlace(mAtoms.voiceCallSession, call, mMaxNumVoiceCallSessions);
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
Rlog.d(TAG, "Add new voice call session: " + call.toString());
}
/** Adds RAT usages to the storage when a call session ends. */
public synchronized void addVoiceCallRatUsage(VoiceCallRatTracker ratUsages) {
mVoiceCallRatTracker.mergeWith(ratUsages);
mAtoms.voiceCallRatUsage = mVoiceCallRatTracker.toProto();
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds an incoming SMS to the storage. */
public synchronized void addIncomingSms(IncomingSms sms) {
sms.hashCode = SmsStats.getSmsHashCode(sms);
mAtoms.incomingSms = insertAtRandomPlace(mAtoms.incomingSms, sms, mMaxNumSms);
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
// To be removed
Rlog.d(TAG, "Add new incoming SMS atom: " + sms.toString());
}
/** Adds an outgoing SMS to the storage. */
public synchronized void addOutgoingSms(OutgoingSms sms) {
sms.hashCode = SmsStats.getSmsHashCode(sms);
// Update the retry id, if needed, so that it's unique and larger than all
// previous ones. (this algorithm ignores the fact that some SMS atoms might
// be dropped due to limit in size of the array).
for (OutgoingSms storedSms : mAtoms.outgoingSms) {
if (storedSms.messageId == sms.messageId && storedSms.retryId >= sms.retryId) {
sms.retryId = storedSms.retryId + 1;
}
}
mAtoms.outgoingSms = insertAtRandomPlace(mAtoms.outgoingSms, sms, mMaxNumSms);
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
// To be removed
Rlog.d(TAG, "Add new outgoing SMS atom: " + sms.toString());
}
/** Adds a service state to the storage, together with data service switch if any. */
public synchronized void addCellularServiceStateAndCellularDataServiceSwitch(
CellularServiceState state, @Nullable CellularDataServiceSwitch serviceSwitch) {
CellularServiceState existingState = find(state);
if (existingState != null) {
existingState.totalTimeMillis += state.totalTimeMillis;
existingState.lastUsedMillis = getWallTimeMillis();
} else {
state.lastUsedMillis = getWallTimeMillis();
mAtoms.cellularServiceState =
insertAtRandomPlace(
mAtoms.cellularServiceState, state, mMaxNumCellularServiceStates);
}
if (serviceSwitch != null) {
CellularDataServiceSwitch existingSwitch = find(serviceSwitch);
if (existingSwitch != null) {
existingSwitch.switchCount += serviceSwitch.switchCount;
existingSwitch.lastUsedMillis = getWallTimeMillis();
} else {
serviceSwitch.lastUsedMillis = getWallTimeMillis();
mAtoms.cellularDataServiceSwitch =
insertAtRandomPlace(
mAtoms.cellularDataServiceSwitch,
serviceSwitch,
mMaxNumCellularDataSwitches);
}
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a data call session to the storage. */
public synchronized void addDataCallSession(DataCallSession dataCall) {
int index = findIndex(dataCall);
if (index >= 0) {
DataCallSession existingCall = mAtoms.dataCallSession[index];
dataCall.ratSwitchCount += existingCall.ratSwitchCount;
dataCall.durationMinutes += existingCall.durationMinutes;
mAtoms.dataCallSession[index] = dataCall;
} else {
mAtoms.dataCallSession =
insertAtRandomPlace(mAtoms.dataCallSession, dataCall, mMaxNumDataCallSessions);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/**
* Adds a new carrier ID mismatch event to the storage.
*
* @return true if the item was not present and was added to the persistent storage, false
* otherwise.
*/
public synchronized boolean addCarrierIdMismatch(CarrierIdMismatch carrierIdMismatch) {
// Check if the details of the SIM cards are already present and in case return.
if (find(carrierIdMismatch) != null) {
return false;
}
// Add the new CarrierIdMismatch at the end of the array, so that the same atom will not be
// sent again in future.
if (mAtoms.carrierIdMismatch.length == mMaxNumCarrierIdMismatches) {
System.arraycopy(
mAtoms.carrierIdMismatch,
1,
mAtoms.carrierIdMismatch,
0,
mMaxNumCarrierIdMismatches - 1);
mAtoms.carrierIdMismatch[mMaxNumCarrierIdMismatches - 1] = carrierIdMismatch;
} else {
mAtoms.carrierIdMismatch =
ArrayUtils.appendElement(
CarrierIdMismatch.class,
mAtoms.carrierIdMismatch,
carrierIdMismatch,
true);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
return true;
}
/** Adds IMS registration stats to the storage. */
public synchronized void addImsRegistrationStats(ImsRegistrationStats stats) {
ImsRegistrationStats existingStats = find(stats);
if (existingStats != null) {
existingStats.registeredMillis += stats.registeredMillis;
existingStats.voiceCapableMillis += stats.voiceCapableMillis;
existingStats.voiceAvailableMillis += stats.voiceAvailableMillis;
existingStats.smsCapableMillis += stats.smsCapableMillis;
existingStats.smsAvailableMillis += stats.smsAvailableMillis;
existingStats.videoCapableMillis += stats.videoCapableMillis;
existingStats.videoAvailableMillis += stats.videoAvailableMillis;
existingStats.utCapableMillis += stats.utCapableMillis;
existingStats.utAvailableMillis += stats.utAvailableMillis;
existingStats.lastUsedMillis = getWallTimeMillis();
} else {
stats.lastUsedMillis = getWallTimeMillis();
mAtoms.imsRegistrationStats =
insertAtRandomPlace(
mAtoms.imsRegistrationStats, stats, mMaxNumImsRegistrationStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds IMS registration termination to the storage. */
public synchronized void addImsRegistrationTermination(ImsRegistrationTermination termination) {
ImsRegistrationTermination existingTermination = find(termination);
if (existingTermination != null) {
existingTermination.count += termination.count;
existingTermination.lastUsedMillis = getWallTimeMillis();
} else {
termination.lastUsedMillis = getWallTimeMillis();
mAtoms.imsRegistrationTermination =
insertAtRandomPlace(
mAtoms.imsRegistrationTermination,
termination,
mMaxNumImsRegistrationTerminations);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/**
* Stores the version of the carrier ID matching table.
*
* @return true if the version is newer than last available version, false otherwise.
*/
public synchronized boolean setCarrierIdTableVersion(int carrierIdTableVersion) {
if (mAtoms.carrierIdTableVersion < carrierIdTableVersion) {
mAtoms.carrierIdTableVersion = carrierIdTableVersion;
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
return true;
} else {
return false;
}
}
/** Adds a new {@link NetworkRequestsV2} to the storage. */
public synchronized void addNetworkRequestsV2(NetworkRequestsV2 networkRequests) {
NetworkRequestsV2 existingMetrics = find(networkRequests);
if (existingMetrics != null) {
existingMetrics.requestCount += networkRequests.requestCount;
} else {
NetworkRequestsV2 newMetrics = new NetworkRequestsV2();
newMetrics.capability = networkRequests.capability;
newMetrics.carrierId = networkRequests.carrierId;
newMetrics.requestCount = networkRequests.requestCount;
mAtoms.networkRequestsV2 =
ArrayUtils.appendElement(
NetworkRequestsV2.class, mAtoms.networkRequestsV2, newMetrics, true);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link ImsRegistrationFeatureTagStats} to the storage. */
public synchronized void addImsRegistrationFeatureTagStats(
ImsRegistrationFeatureTagStats stats) {
ImsRegistrationFeatureTagStats existingStats = find(stats);
if (existingStats != null) {
existingStats.registeredMillis += stats.registeredMillis;
} else {
mAtoms.imsRegistrationFeatureTagStats =
insertAtRandomPlace(mAtoms.imsRegistrationFeatureTagStats,
stats, mMaxNumImsRegistrationFeatureStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link RcsClientProvisioningStats} to the storage. */
public synchronized void addRcsClientProvisioningStats(RcsClientProvisioningStats stats) {
RcsClientProvisioningStats existingStats = find(stats);
if (existingStats != null) {
existingStats.count += 1;
} else {
mAtoms.rcsClientProvisioningStats =
insertAtRandomPlace(mAtoms.rcsClientProvisioningStats, stats,
mMaxNumRcsClientProvisioningStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link RcsAcsProvisioningStats} to the storage. */
public synchronized void addRcsAcsProvisioningStats(RcsAcsProvisioningStats stats) {
RcsAcsProvisioningStats existingStats = find(stats);
if (existingStats != null) {
existingStats.count += 1;
existingStats.stateTimerMillis += stats.stateTimerMillis;
} else {
// prevent that wrong count from caller effects total count
stats.count = 1;
mAtoms.rcsAcsProvisioningStats =
insertAtRandomPlace(mAtoms.rcsAcsProvisioningStats, stats,
mMaxNumRcsAcsProvisioningStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link SipDelegateStats} to the storage. */
public synchronized void addSipDelegateStats(SipDelegateStats stats) {
mAtoms.sipDelegateStats = insertAtRandomPlace(mAtoms.sipDelegateStats, stats,
mMaxNumSipDelegateStats);
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link SipTransportFeatureTagStats} to the storage. */
public synchronized void addSipTransportFeatureTagStats(SipTransportFeatureTagStats stats) {
SipTransportFeatureTagStats lastStat = find(stats);
if (lastStat != null) {
lastStat.associatedMillis += stats.associatedMillis;
} else {
mAtoms.sipTransportFeatureTagStats =
insertAtRandomPlace(mAtoms.sipTransportFeatureTagStats, stats,
mMaxNumSipTransportFeatureTagStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link SipMessageResponse} to the storage. */
public synchronized void addSipMessageResponse(SipMessageResponse stats) {
SipMessageResponse existingStats = find(stats);
if (existingStats != null) {
existingStats.count += 1;
} else {
mAtoms.sipMessageResponse = insertAtRandomPlace(mAtoms.sipMessageResponse, stats,
mMaxNumSipMessageResponseStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link SipTransportSession} to the storage. */
public synchronized void addCompleteSipTransportSession(SipTransportSession stats) {
SipTransportSession existingStats = find(stats);
if (existingStats != null) {
existingStats.sessionCount += 1;
if (stats.isEndedGracefully) {
existingStats.endedGracefullyCount += 1;
}
} else {
mAtoms.sipTransportSession =
insertAtRandomPlace(mAtoms.sipTransportSession, stats,
mMaxNumSipTransportSessionStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link ImsDedicatedBearerListenerEvent} to the storage. */
public synchronized void addImsDedicatedBearerListenerEvent(
ImsDedicatedBearerListenerEvent stats) {
ImsDedicatedBearerListenerEvent existingStats = find(stats);
if (existingStats != null) {
existingStats.eventCount += 1;
} else {
mAtoms.imsDedicatedBearerListenerEvent =
insertAtRandomPlace(mAtoms.imsDedicatedBearerListenerEvent,
stats, mMaxNumDedicatedBearerListenerEventStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link ImsDedicatedBearerEvent} to the storage. */
public synchronized void addImsDedicatedBearerEvent(ImsDedicatedBearerEvent stats) {
ImsDedicatedBearerEvent existingStats = find(stats);
if (existingStats != null) {
existingStats.count += 1;
} else {
mAtoms.imsDedicatedBearerEvent =
insertAtRandomPlace(mAtoms.imsDedicatedBearerEvent, stats,
mMaxNumDedicatedBearerEventStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link ImsRegistrationServiceDescStats} to the storage. */
public synchronized void addImsRegistrationServiceDescStats(
ImsRegistrationServiceDescStats stats) {
ImsRegistrationServiceDescStats existingStats = find(stats);
if (existingStats != null) {
existingStats.publishedMillis += stats.publishedMillis;
} else {
mAtoms.imsRegistrationServiceDescStats =
insertAtRandomPlace(mAtoms.imsRegistrationServiceDescStats,
stats, mMaxNumImsRegistrationServiceDescStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link UceEventStats} to the storage. */
public synchronized void addUceEventStats(UceEventStats stats) {
UceEventStats existingStats = find(stats);
if (existingStats != null) {
existingStats.count += 1;
} else {
mAtoms.uceEventStats =
insertAtRandomPlace(mAtoms.uceEventStats, stats, mMaxNumUceEventStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link PresenceNotifyEvent} to the storage. */
public synchronized void addPresenceNotifyEvent(PresenceNotifyEvent stats) {
PresenceNotifyEvent existingStats = find(stats);
if (existingStats != null) {
existingStats.rcsCapsCount += stats.rcsCapsCount;
existingStats.mmtelCapsCount += stats.mmtelCapsCount;
existingStats.noCapsCount += stats.noCapsCount;
existingStats.count += stats.count;
} else {
mAtoms.presenceNotifyEvent =
insertAtRandomPlace(mAtoms.presenceNotifyEvent, stats,
mMaxNumPresenceNotifyEventStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/** Adds a new {@link GbaEvent} to the storage. */
public synchronized void addGbaEvent(GbaEvent stats) {
GbaEvent existingStats = find(stats);
if (existingStats != null) {
existingStats.count += 1;
} else {
mAtoms.gbaEvent =
insertAtRandomPlace(mAtoms.gbaEvent, stats, mMaxNumGbaEventStats);
}
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
/**
* Sets the unmetered networks bitmask for a given phone id. If the carrier id
* doesn't match the existing UnmeteredNetworks' carrier id, the bitmask is
* first reset to 0.
*/
public synchronized void addUnmeteredNetworks(
int phoneId, int carrierId, @NetworkTypeBitMask long bitmask) {
UnmeteredNetworks stats = findUnmeteredNetworks(phoneId);
boolean needToSave = true;
if (stats == null) {
stats = new UnmeteredNetworks();
stats.phoneId = phoneId;
stats.carrierId = carrierId;
stats.unmeteredNetworksBitmask = bitmask;
mAtoms.unmeteredNetworks =
ArrayUtils.appendElement(
UnmeteredNetworks.class, mAtoms.unmeteredNetworks, stats, true);
} else {
// Reset the bitmask to 0 if carrier id doesn't match.
if (stats.carrierId != carrierId) {
stats.carrierId = carrierId;
stats.unmeteredNetworksBitmask = 0;
}
if ((stats.unmeteredNetworksBitmask | bitmask) != stats.unmeteredNetworksBitmask) {
stats.unmeteredNetworksBitmask |= bitmask;
} else {
needToSave = false;
}
}
// Only save if something changes.
if (needToSave) {
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
}
/**
* Returns and clears the voice call sessions if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized VoiceCallSession[] getVoiceCallSessions(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.voiceCallSessionPullTimestampMillis > minIntervalMillis) {
mAtoms.voiceCallSessionPullTimestampMillis = getWallTimeMillis();
VoiceCallSession[] previousCalls = mAtoms.voiceCallSession;
mAtoms.voiceCallSession = new VoiceCallSession[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousCalls;
} else {
return null;
}
}
/**
* Returns and clears the voice call RAT usages if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized VoiceCallRatUsage[] getVoiceCallRatUsages(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.voiceCallRatUsagePullTimestampMillis > minIntervalMillis) {
mAtoms.voiceCallRatUsagePullTimestampMillis = getWallTimeMillis();
VoiceCallRatUsage[] previousUsages = mAtoms.voiceCallRatUsage;
mVoiceCallRatTracker.clear();
mAtoms.voiceCallRatUsage = new VoiceCallRatUsage[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousUsages;
} else {
return null;
}
}
/**
* Returns and clears the incoming SMS if last pulled longer than {@code minIntervalMillis} ago,
* otherwise returns {@code null}.
*/
@Nullable
public synchronized IncomingSms[] getIncomingSms(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.incomingSmsPullTimestampMillis > minIntervalMillis) {
mAtoms.incomingSmsPullTimestampMillis = getWallTimeMillis();
IncomingSms[] previousIncomingSms = mAtoms.incomingSms;
mAtoms.incomingSms = new IncomingSms[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousIncomingSms;
} else {
return null;
}
}
/**
* Returns and clears the outgoing SMS if last pulled longer than {@code minIntervalMillis} ago,
* otherwise returns {@code null}.
*/
@Nullable
public synchronized OutgoingSms[] getOutgoingSms(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.outgoingSmsPullTimestampMillis > minIntervalMillis) {
mAtoms.outgoingSmsPullTimestampMillis = getWallTimeMillis();
OutgoingSms[] previousOutgoingSms = mAtoms.outgoingSms;
mAtoms.outgoingSms = new OutgoingSms[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousOutgoingSms;
} else {
return null;
}
}
/**
* Returns and clears the data call session if last pulled longer than {@code minIntervalMillis}
* ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized DataCallSession[] getDataCallSessions(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.dataCallSessionPullTimestampMillis > minIntervalMillis) {
mAtoms.dataCallSessionPullTimestampMillis = getWallTimeMillis();
DataCallSession[] previousDataCallSession = mAtoms.dataCallSession;
mAtoms.dataCallSession = new DataCallSession[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
for (DataCallSession dataCallSession : previousDataCallSession) {
// sort to de-correlate any potential pattern for UII concern
Arrays.sort(dataCallSession.handoverFailureCauses);
}
return previousDataCallSession;
} else {
return null;
}
}
/**
* Returns and clears the service state durations if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized CellularServiceState[] getCellularServiceStates(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.cellularServiceStatePullTimestampMillis
> minIntervalMillis) {
mAtoms.cellularServiceStatePullTimestampMillis = getWallTimeMillis();
CellularServiceState[] previousStates = mAtoms.cellularServiceState;
Arrays.stream(previousStates).forEach(state -> state.lastUsedMillis = 0L);
mAtoms.cellularServiceState = new CellularServiceState[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStates;
} else {
return null;
}
}
/**
* Returns and clears the service state durations if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized CellularDataServiceSwitch[] getCellularDataServiceSwitches(
long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.cellularDataServiceSwitchPullTimestampMillis
> minIntervalMillis) {
mAtoms.cellularDataServiceSwitchPullTimestampMillis = getWallTimeMillis();
CellularDataServiceSwitch[] previousSwitches = mAtoms.cellularDataServiceSwitch;
Arrays.stream(previousSwitches)
.forEach(serviceSwitch -> serviceSwitch.lastUsedMillis = 0L);
mAtoms.cellularDataServiceSwitch = new CellularDataServiceSwitch[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousSwitches;
} else {
return null;
}
}
/**
* Returns and clears the IMS registration statistics normalized to 24h cycle if last
* pulled longer than {@code minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized ImsRegistrationStats[] getImsRegistrationStats(long minIntervalMillis) {
long intervalMillis =
getWallTimeMillis() - mAtoms.imsRegistrationStatsPullTimestampMillis;
if (intervalMillis > minIntervalMillis) {
mAtoms.imsRegistrationStatsPullTimestampMillis = getWallTimeMillis();
ImsRegistrationStats[] previousStats = mAtoms.imsRegistrationStats;
Arrays.stream(previousStats).forEach(stats -> stats.lastUsedMillis = 0L);
mAtoms.imsRegistrationStats = new ImsRegistrationStats[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return normalizeData(previousStats, intervalMillis);
} else {
return null;
}
}
/**
* Returns and clears the IMS registration terminations if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized ImsRegistrationTermination[] getImsRegistrationTerminations(
long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.imsRegistrationTerminationPullTimestampMillis
> minIntervalMillis) {
mAtoms.imsRegistrationTerminationPullTimestampMillis = getWallTimeMillis();
ImsRegistrationTermination[] previousTerminations = mAtoms.imsRegistrationTermination;
Arrays.stream(previousTerminations)
.forEach(termination -> termination.lastUsedMillis = 0L);
mAtoms.imsRegistrationTermination = new ImsRegistrationTermination[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousTerminations;
} else {
return null;
}
}
/**
* Returns and clears the network requests if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized NetworkRequestsV2[] getNetworkRequestsV2(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.networkRequestsV2PullTimestampMillis > minIntervalMillis) {
mAtoms.networkRequestsV2PullTimestampMillis = getWallTimeMillis();
NetworkRequestsV2[] previousNetworkRequests = mAtoms.networkRequestsV2;
mAtoms.networkRequestsV2 = new NetworkRequestsV2[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousNetworkRequests;
} else {
return null;
}
}
/**
* Returns and clears the ImsRegistrationFeatureTagStats if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized ImsRegistrationFeatureTagStats[] getImsRegistrationFeatureTagStats(
long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.imsRegistrationFeatureTagStatsPullTimestampMillis
> minIntervalMillis) {
mAtoms.imsRegistrationFeatureTagStatsPullTimestampMillis = getWallTimeMillis();
ImsRegistrationFeatureTagStats[] previousStats =
mAtoms.imsRegistrationFeatureTagStats;
mAtoms.imsRegistrationFeatureTagStats = new ImsRegistrationFeatureTagStats[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the RcsClientProvisioningStats if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized RcsClientProvisioningStats[] getRcsClientProvisioningStats(
long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.rcsClientProvisioningStatsPullTimestampMillis
> minIntervalMillis) {
mAtoms.rcsClientProvisioningStatsPullTimestampMillis = getWallTimeMillis();
RcsClientProvisioningStats[] previousStats = mAtoms.rcsClientProvisioningStats;
mAtoms.rcsClientProvisioningStats = new RcsClientProvisioningStats[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the RcsAcsProvisioningStats if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized RcsAcsProvisioningStats[] getRcsAcsProvisioningStats(
long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.rcsAcsProvisioningStatsPullTimestampMillis
> minIntervalMillis) {
mAtoms.rcsAcsProvisioningStatsPullTimestampMillis = getWallTimeMillis();
RcsAcsProvisioningStats[] previousStats = mAtoms.rcsAcsProvisioningStats;
mAtoms.rcsAcsProvisioningStats = new RcsAcsProvisioningStats[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the SipDelegateStats if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized SipDelegateStats[] getSipDelegateStats(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.sipDelegateStatsPullTimestampMillis
> minIntervalMillis) {
mAtoms.sipDelegateStatsPullTimestampMillis = getWallTimeMillis();
SipDelegateStats[] previousStats = mAtoms.sipDelegateStats;
mAtoms.sipDelegateStats = new SipDelegateStats[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the SipTransportFeatureTagStats if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized SipTransportFeatureTagStats[] getSipTransportFeatureTagStats(
long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.sipTransportFeatureTagStatsPullTimestampMillis
> minIntervalMillis) {
mAtoms.sipTransportFeatureTagStatsPullTimestampMillis = getWallTimeMillis();
SipTransportFeatureTagStats[] previousStats = mAtoms.sipTransportFeatureTagStats;
mAtoms.sipTransportFeatureTagStats = new SipTransportFeatureTagStats[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the SipMessageResponse if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized SipMessageResponse[] getSipMessageResponse(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.sipMessageResponsePullTimestampMillis
> minIntervalMillis) {
mAtoms.sipMessageResponsePullTimestampMillis = getWallTimeMillis();
SipMessageResponse[] previousStats =
mAtoms.sipMessageResponse;
mAtoms.sipMessageResponse = new SipMessageResponse[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the SipTransportSession if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized SipTransportSession[] getSipTransportSession(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.sipTransportSessionPullTimestampMillis
> minIntervalMillis) {
mAtoms.sipTransportSessionPullTimestampMillis = getWallTimeMillis();
SipTransportSession[] previousStats =
mAtoms.sipTransportSession;
mAtoms.sipTransportSession = new SipTransportSession[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the ImsDedicatedBearerListenerEvent if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized ImsDedicatedBearerListenerEvent[] getImsDedicatedBearerListenerEvent(
long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.imsDedicatedBearerListenerEventPullTimestampMillis
> minIntervalMillis) {
mAtoms.imsDedicatedBearerListenerEventPullTimestampMillis = getWallTimeMillis();
ImsDedicatedBearerListenerEvent[] previousStats =
mAtoms.imsDedicatedBearerListenerEvent;
mAtoms.imsDedicatedBearerListenerEvent = new ImsDedicatedBearerListenerEvent[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the ImsDedicatedBearerEvent if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized ImsDedicatedBearerEvent[] getImsDedicatedBearerEvent(
long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.imsDedicatedBearerEventPullTimestampMillis
> minIntervalMillis) {
mAtoms.imsDedicatedBearerEventPullTimestampMillis = getWallTimeMillis();
ImsDedicatedBearerEvent[] previousStats =
mAtoms.imsDedicatedBearerEvent;
mAtoms.imsDedicatedBearerEvent = new ImsDedicatedBearerEvent[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the ImsRegistrationServiceDescStats if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized ImsRegistrationServiceDescStats[] getImsRegistrationServiceDescStats(long
minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.imsRegistrationServiceDescStatsPullTimestampMillis
> minIntervalMillis) {
mAtoms.imsRegistrationServiceDescStatsPullTimestampMillis = getWallTimeMillis();
ImsRegistrationServiceDescStats[] previousStats =
mAtoms.imsRegistrationServiceDescStats;
mAtoms.imsRegistrationServiceDescStats = new ImsRegistrationServiceDescStats[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the UceEventStats if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized UceEventStats[] getUceEventStats(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.uceEventStatsPullTimestampMillis > minIntervalMillis) {
mAtoms.uceEventStatsPullTimestampMillis = getWallTimeMillis();
UceEventStats[] previousStats = mAtoms.uceEventStats;
mAtoms.uceEventStats = new UceEventStats[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the PresenceNotifyEvent if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized PresenceNotifyEvent[] getPresenceNotifyEvent(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.presenceNotifyEventPullTimestampMillis
> minIntervalMillis) {
mAtoms.presenceNotifyEventPullTimestampMillis = getWallTimeMillis();
PresenceNotifyEvent[] previousStats = mAtoms.presenceNotifyEvent;
mAtoms.presenceNotifyEvent = new PresenceNotifyEvent[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns and clears the GbaEvent if last pulled longer than {@code
* minIntervalMillis} ago, otherwise returns {@code null}.
*/
@Nullable
public synchronized GbaEvent[] getGbaEvent(long minIntervalMillis) {
if (getWallTimeMillis() - mAtoms.gbaEventPullTimestampMillis > minIntervalMillis) {
mAtoms.gbaEventPullTimestampMillis = getWallTimeMillis();
GbaEvent[] previousStats = mAtoms.gbaEvent;
mAtoms.gbaEvent = new GbaEvent[0];
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return previousStats;
} else {
return null;
}
}
/**
* Returns the unmetered networks bitmask for a given phone id. Returns 0 if there is
* no existing UnmeteredNetworks for the given phone id or the carrier id doesn't match.
* Existing UnmeteredNetworks is discarded after.
*/
public synchronized @NetworkTypeBitMask long getUnmeteredNetworks(int phoneId, int carrierId) {
UnmeteredNetworks existingStats = findUnmeteredNetworks(phoneId);
if (existingStats == null) {
return 0L;
}
@NetworkTypeBitMask
long bitmask =
existingStats.carrierId != carrierId ? 0L : existingStats.unmeteredNetworksBitmask;
mAtoms.unmeteredNetworks =
sanitizeAtoms(
ArrayUtils.removeElement(
UnmeteredNetworks.class,
mAtoms.unmeteredNetworks,
existingStats),
UnmeteredNetworks.class);
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
return bitmask;
}
/** Saves {@link PersistAtoms} to a file in private storage immediately. */
public synchronized void flushAtoms() {
saveAtomsToFile(0);
}
/** Clears atoms for testing purpose. */
public synchronized void clearAtoms() {
mAtoms = makeNewPersistAtoms();
saveAtomsToFile(0);
}
/** Loads {@link PersistAtoms} from a file in private storage. */
private PersistAtoms loadAtomsFromFile() {
try {
PersistAtoms atoms =
PersistAtoms.parseFrom(
Files.readAllBytes(mContext.getFileStreamPath(FILENAME).toPath()));
// Start from scratch if build changes, since mixing atoms from different builds could
// produce strange results
if (!Build.FINGERPRINT.equals(atoms.buildFingerprint)) {
Rlog.d(TAG, "Build changed");
return makeNewPersistAtoms();
}
// check all the fields in case of situations such as OTA or crash during saving
atoms.voiceCallRatUsage =
sanitizeAtoms(atoms.voiceCallRatUsage, VoiceCallRatUsage.class);
atoms.voiceCallSession =
sanitizeAtoms(
atoms.voiceCallSession,
VoiceCallSession.class,
mMaxNumVoiceCallSessions);
atoms.incomingSms = sanitizeAtoms(atoms.incomingSms, IncomingSms.class, mMaxNumSms);
atoms.outgoingSms = sanitizeAtoms(atoms.outgoingSms, OutgoingSms.class, mMaxNumSms);
atoms.carrierIdMismatch =
sanitizeAtoms(
atoms.carrierIdMismatch,
CarrierIdMismatch.class,
mMaxNumCarrierIdMismatches);
atoms.dataCallSession =
sanitizeAtoms(
atoms.dataCallSession,
DataCallSession.class,
mMaxNumDataCallSessions);
atoms.cellularServiceState =
sanitizeAtoms(
atoms.cellularServiceState,
CellularServiceState.class,
mMaxNumCellularServiceStates);
atoms.cellularDataServiceSwitch =
sanitizeAtoms(
atoms.cellularDataServiceSwitch,
CellularDataServiceSwitch.class,
mMaxNumCellularDataSwitches);
atoms.imsRegistrationStats =
sanitizeAtoms(
atoms.imsRegistrationStats,
ImsRegistrationStats.class,
mMaxNumImsRegistrationStats);
atoms.imsRegistrationTermination =
sanitizeAtoms(
atoms.imsRegistrationTermination,
ImsRegistrationTermination.class,
mMaxNumImsRegistrationTerminations);
atoms.networkRequestsV2 =
sanitizeAtoms(atoms.networkRequestsV2, NetworkRequestsV2.class);
atoms.imsRegistrationFeatureTagStats =
sanitizeAtoms(
atoms.imsRegistrationFeatureTagStats,
ImsRegistrationFeatureTagStats.class,
mMaxNumImsRegistrationFeatureStats);
atoms.rcsClientProvisioningStats =
sanitizeAtoms(
atoms.rcsClientProvisioningStats,
RcsClientProvisioningStats.class,
mMaxNumRcsClientProvisioningStats);
atoms.rcsAcsProvisioningStats =
sanitizeAtoms(
atoms.rcsAcsProvisioningStats,
RcsAcsProvisioningStats.class,
mMaxNumRcsAcsProvisioningStats);
atoms.sipDelegateStats =
sanitizeAtoms(
atoms.sipDelegateStats,
SipDelegateStats.class,
mMaxNumSipDelegateStats);
atoms.sipTransportFeatureTagStats =
sanitizeAtoms(
atoms.sipTransportFeatureTagStats,
SipTransportFeatureTagStats.class,
mMaxNumSipTransportFeatureTagStats);
atoms.sipMessageResponse =
sanitizeAtoms(
atoms.sipMessageResponse,
SipMessageResponse.class,
mMaxNumSipMessageResponseStats);
atoms.sipTransportSession =
sanitizeAtoms(
atoms.sipTransportSession,
SipTransportSession.class,
mMaxNumSipTransportSessionStats);
atoms.imsDedicatedBearerListenerEvent =
sanitizeAtoms(
atoms.imsDedicatedBearerListenerEvent,
ImsDedicatedBearerListenerEvent.class,
mMaxNumDedicatedBearerListenerEventStats);
atoms.imsDedicatedBearerEvent =
sanitizeAtoms(
atoms.imsDedicatedBearerEvent,
ImsDedicatedBearerEvent.class,
mMaxNumDedicatedBearerEventStats);
atoms.imsRegistrationServiceDescStats =
sanitizeAtoms(
atoms.imsRegistrationServiceDescStats,
ImsRegistrationServiceDescStats.class,
mMaxNumImsRegistrationServiceDescStats);
atoms.uceEventStats =
sanitizeAtoms(
atoms.uceEventStats,
UceEventStats.class,
mMaxNumUceEventStats);
atoms.presenceNotifyEvent =
sanitizeAtoms(
atoms.presenceNotifyEvent,
PresenceNotifyEvent.class,
mMaxNumPresenceNotifyEventStats);
atoms.gbaEvent =
sanitizeAtoms(
atoms.gbaEvent,
GbaEvent.class,
mMaxNumGbaEventStats);
atoms.unmeteredNetworks =
sanitizeAtoms(
atoms.unmeteredNetworks,
UnmeteredNetworks.class
);
// out of caution, sanitize also the timestamps
atoms.voiceCallRatUsagePullTimestampMillis =
sanitizeTimestamp(atoms.voiceCallRatUsagePullTimestampMillis);
atoms.voiceCallSessionPullTimestampMillis =
sanitizeTimestamp(atoms.voiceCallSessionPullTimestampMillis);
atoms.incomingSmsPullTimestampMillis =
sanitizeTimestamp(atoms.incomingSmsPullTimestampMillis);
atoms.outgoingSmsPullTimestampMillis =
sanitizeTimestamp(atoms.outgoingSmsPullTimestampMillis);
atoms.dataCallSessionPullTimestampMillis =
sanitizeTimestamp(atoms.dataCallSessionPullTimestampMillis);
atoms.cellularServiceStatePullTimestampMillis =
sanitizeTimestamp(atoms.cellularServiceStatePullTimestampMillis);
atoms.cellularDataServiceSwitchPullTimestampMillis =
sanitizeTimestamp(atoms.cellularDataServiceSwitchPullTimestampMillis);
atoms.imsRegistrationStatsPullTimestampMillis =
sanitizeTimestamp(atoms.imsRegistrationStatsPullTimestampMillis);
atoms.imsRegistrationTerminationPullTimestampMillis =
sanitizeTimestamp(atoms.imsRegistrationTerminationPullTimestampMillis);
atoms.networkRequestsV2PullTimestampMillis =
sanitizeTimestamp(atoms.networkRequestsV2PullTimestampMillis);
atoms.imsRegistrationFeatureTagStatsPullTimestampMillis =
sanitizeTimestamp(atoms.imsRegistrationFeatureTagStatsPullTimestampMillis);
atoms.rcsClientProvisioningStatsPullTimestampMillis =
sanitizeTimestamp(atoms.rcsClientProvisioningStatsPullTimestampMillis);
atoms.rcsAcsProvisioningStatsPullTimestampMillis =
sanitizeTimestamp(atoms.rcsAcsProvisioningStatsPullTimestampMillis);
atoms.sipDelegateStatsPullTimestampMillis =
sanitizeTimestamp(atoms.sipDelegateStatsPullTimestampMillis);
atoms.sipTransportFeatureTagStatsPullTimestampMillis =
sanitizeTimestamp(atoms.sipTransportFeatureTagStatsPullTimestampMillis);
atoms.sipMessageResponsePullTimestampMillis =
sanitizeTimestamp(atoms.sipMessageResponsePullTimestampMillis);
atoms.sipTransportSessionPullTimestampMillis =
sanitizeTimestamp(atoms.sipTransportSessionPullTimestampMillis);
atoms.imsDedicatedBearerListenerEventPullTimestampMillis =
sanitizeTimestamp(atoms.imsDedicatedBearerListenerEventPullTimestampMillis);
atoms.imsDedicatedBearerEventPullTimestampMillis =
sanitizeTimestamp(atoms.imsDedicatedBearerEventPullTimestampMillis);
atoms.imsRegistrationServiceDescStatsPullTimestampMillis =
sanitizeTimestamp(atoms.imsRegistrationServiceDescStatsPullTimestampMillis);
atoms.uceEventStatsPullTimestampMillis =
sanitizeTimestamp(atoms.uceEventStatsPullTimestampMillis);
atoms.presenceNotifyEventPullTimestampMillis =
sanitizeTimestamp(atoms.presenceNotifyEventPullTimestampMillis);
atoms.gbaEventPullTimestampMillis =
sanitizeTimestamp(atoms.gbaEventPullTimestampMillis);
return atoms;
} catch (NoSuchFileException e) {
Rlog.d(TAG, "PersistAtoms file not found");
} catch (IOException | NullPointerException e) {
Rlog.e(TAG, "cannot load/parse PersistAtoms", e);
}
return makeNewPersistAtoms();
}
/**
* Posts message to save a copy of {@link PersistAtoms} to a file after a delay or immediately.
*
* <p>The delay is introduced to avoid too frequent operations to disk, which would negatively
* impact the power consumption.
*/
private synchronized void saveAtomsToFile(int delayMillis) {
mHandler.removeCallbacks(mSaveRunnable);
if (delayMillis > 0 && !mSaveImmediately) {
if (mHandler.postDelayed(mSaveRunnable, delayMillis)) {
return;
}
}
// In case of error posting the event or if delay is 0, save immediately
saveAtomsToFileNow();
}
/** Saves a copy of {@link PersistAtoms} to a file in private storage. */
private synchronized void saveAtomsToFileNow() {
try (FileOutputStream stream = mContext.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
stream.write(PersistAtoms.toByteArray(mAtoms));
} catch (IOException e) {
Rlog.e(TAG, "cannot save PersistAtoms", e);
}
}
/**
* Returns the service state that has the same dimension values with the given one, or {@code
* null} if it does not exist.
*/
private @Nullable CellularServiceState find(CellularServiceState key) {
for (CellularServiceState state : mAtoms.cellularServiceState) {
if (state.voiceRat == key.voiceRat
&& state.dataRat == key.dataRat
&& state.voiceRoamingType == key.voiceRoamingType
&& state.dataRoamingType == key.dataRoamingType
&& state.isEndc == key.isEndc
&& state.simSlotIndex == key.simSlotIndex
&& state.isMultiSim == key.isMultiSim
&& state.carrierId == key.carrierId
&& state.isEmergencyOnly == key.isEmergencyOnly) {
return state;
}
}
return null;
}
/**
* Returns the data service switch that has the same dimension values with the given one, or
* {@code null} if it does not exist.
*/
private @Nullable CellularDataServiceSwitch find(CellularDataServiceSwitch key) {
for (CellularDataServiceSwitch serviceSwitch : mAtoms.cellularDataServiceSwitch) {
if (serviceSwitch.ratFrom == key.ratFrom
&& serviceSwitch.ratTo == key.ratTo
&& serviceSwitch.simSlotIndex == key.simSlotIndex
&& serviceSwitch.isMultiSim == key.isMultiSim
&& serviceSwitch.carrierId == key.carrierId) {
return serviceSwitch;
}
}
return null;
}
/**
* Returns the carrier ID mismatch event that has the same dimension values with the given one,
* or {@code null} if it does not exist.
*/
private @Nullable CarrierIdMismatch find(CarrierIdMismatch key) {
for (CarrierIdMismatch mismatch : mAtoms.carrierIdMismatch) {
if (mismatch.mccMnc.equals(key.mccMnc)
&& mismatch.gid1.equals(key.gid1)
&& mismatch.spn.equals(key.spn)
&& mismatch.pnn.equals(key.pnn)) {
return mismatch;
}
}
return null;
}
/**
* Returns the IMS registration stats that has the same dimension values with the given one, or
* {@code null} if it does not exist.
*/
private @Nullable ImsRegistrationStats find(ImsRegistrationStats key) {
for (ImsRegistrationStats stats : mAtoms.imsRegistrationStats) {
if (stats.carrierId == key.carrierId
&& stats.simSlotIndex == key.simSlotIndex
&& stats.rat == key.rat) {
return stats;
}
}
return null;
}
/**
* Returns the IMS registration termination that has the same dimension values with the given
* one, or {@code null} if it does not exist.
*/
private @Nullable ImsRegistrationTermination find(ImsRegistrationTermination key) {
for (ImsRegistrationTermination termination : mAtoms.imsRegistrationTermination) {
if (termination.carrierId == key.carrierId
&& termination.isMultiSim == key.isMultiSim
&& termination.ratAtEnd == key.ratAtEnd
&& termination.setupFailed == key.setupFailed
&& termination.reasonCode == key.reasonCode
&& termination.extraCode == key.extraCode
&& termination.extraMessage.equals(key.extraMessage)) {
return termination;
}
}
return null;
}
/**
* Returns the network requests event that has the same carrier id and capability as the given
* one, or {@code null} if it does not exist.
*/
private @Nullable NetworkRequestsV2 find(NetworkRequestsV2 key) {
for (NetworkRequestsV2 item : mAtoms.networkRequestsV2) {
if (item.carrierId == key.carrierId && item.capability == key.capability) {
return item;
}
}
return null;
}
/**
* Returns the index of data call session that has the same random dimension as the given one,
* or -1 if it does not exist.
*/
private int findIndex(DataCallSession key) {
for (int i = 0; i < mAtoms.dataCallSession.length; i++) {
if (mAtoms.dataCallSession[i].dimension == key.dimension) {
return i;
}
}
return -1;
}
/**
* Returns the Dedicated Bearer Listener event that has the same carrier id, slot id, rat, qci
* and established state as the given one, or {@code null} if it does not exist.
*/
private @Nullable ImsDedicatedBearerListenerEvent find(ImsDedicatedBearerListenerEvent key) {
for (ImsDedicatedBearerListenerEvent stats : mAtoms.imsDedicatedBearerListenerEvent) {
if (stats.carrierId == key.carrierId
&& stats.slotId == key.slotId
&& stats.ratAtEnd == key.ratAtEnd
&& stats.qci == key.qci
&& stats.dedicatedBearerEstablished == key.dedicatedBearerEstablished) {
return stats;
}
}
return null;
}
/**
* Returns the Dedicated Bearer event that has the same carrier id, slot id, rat,
* qci, bearer state, local/remote connection and exsting listener as the given one,
* or {@code null} if it does not exist.
*/
private @Nullable ImsDedicatedBearerEvent find(ImsDedicatedBearerEvent key) {
for (ImsDedicatedBearerEvent stats : mAtoms.imsDedicatedBearerEvent) {
if (stats.carrierId == key.carrierId
&& stats.slotId == key.slotId
&& stats.ratAtEnd == key.ratAtEnd
&& stats.qci == key.qci
&& stats.bearerState == key.bearerState
&& stats.localConnectionInfoReceived == key.localConnectionInfoReceived
&& stats.remoteConnectionInfoReceived == key.remoteConnectionInfoReceived
&& stats.hasListeners == key.hasListeners) {
return stats;
}
}
return null;
}
/**
* Returns the Registration Feature Tag that has the same carrier id, slot id,
* feature tag name or custom feature tag name and registration tech as the given one,
* or {@code null} if it does not exist.
*/
private @Nullable ImsRegistrationFeatureTagStats find(ImsRegistrationFeatureTagStats key) {
for (ImsRegistrationFeatureTagStats stats : mAtoms.imsRegistrationFeatureTagStats) {
if (stats.carrierId == key.carrierId
&& stats.slotId == key.slotId
&& stats.featureTagName == key.featureTagName
&& stats.registrationTech == key.registrationTech) {
return stats;
}
}
return null;
}
/**
* Returns Client Provisioning that has the same carrier id, slot id and event as the given
* one, or {@code null} if it does not exist.
*/
private @Nullable RcsClientProvisioningStats find(RcsClientProvisioningStats key) {
for (RcsClientProvisioningStats stats : mAtoms.rcsClientProvisioningStats) {
if (stats.carrierId == key.carrierId
&& stats.slotId == key.slotId
&& stats.event == key.event) {
return stats;
}
}
return null;
}
/**
* Returns ACS Provisioning that has the same carrier id, slot id, response code, response type
* and SR supported as the given one, or {@code null} if it does not exist.
*/
private @Nullable RcsAcsProvisioningStats find(RcsAcsProvisioningStats key) {
for (RcsAcsProvisioningStats stats : mAtoms.rcsAcsProvisioningStats) {
if (stats.carrierId == key.carrierId
&& stats.slotId == key.slotId
&& stats.responseCode == key.responseCode
&& stats.responseType == key.responseType
&& stats.isSingleRegistrationEnabled == key.isSingleRegistrationEnabled) {
return stats;
}
}
return null;
}
/**
* Returns Sip Message Response that has the same carrier id, slot id, method, response,
* direction and error as the given one, or {@code null} if it does not exist.
*/
private @Nullable SipMessageResponse find(SipMessageResponse key) {
for (SipMessageResponse stats : mAtoms.sipMessageResponse) {
if (stats.carrierId == key.carrierId
&& stats.slotId == key.slotId
&& stats.sipMessageMethod == key.sipMessageMethod
&& stats.sipMessageResponse == key.sipMessageResponse
&& stats.sipMessageDirection == key.sipMessageDirection
&& stats.messageError == key.messageError) {
return stats;
}
}
return null;
}
/**
* Returns Sip Transport Session that has the same carrier id, slot id, method, direction and
* response as the given one, or {@code null} if it does not exist.
*/
private @Nullable SipTransportSession find(SipTransportSession key) {
for (SipTransportSession stats : mAtoms.sipTransportSession) {
if (stats.carrierId == key.carrierId
&& stats.slotId == key.slotId
&& stats.sessionMethod == key.sessionMethod
&& stats.sipMessageDirection == key.sipMessageDirection
&& stats.sipResponse == key.sipResponse) {
return stats;
}
}
return null;
}
/**
* Returns Registration Service Desc Stats that has the same carrier id, slot id, service id or
* custom service id, service id version and registration tech as the given one,
* or {@code null} if it does not exist.
*/
private @Nullable ImsRegistrationServiceDescStats find(ImsRegistrationServiceDescStats key) {
for (ImsRegistrationServiceDescStats stats : mAtoms.imsRegistrationServiceDescStats) {
if (stats.carrierId == key.carrierId
&& stats.slotId == key.slotId
&& stats.serviceIdName == key.serviceIdName
&& stats.serviceIdVersion == key.serviceIdVersion
&& stats.registrationTech == key.registrationTech) {
return stats;
}
}
return null;
}
/**
* Returns UCE Event Stats that has the same carrier id, slot id, event result, command code and
* network response as the given one, or {@code null} if it does not exist.
*/
private @Nullable UceEventStats find(UceEventStats key) {
for (UceEventStats stats : mAtoms.uceEventStats) {
if (stats.carrierId == key.carrierId
&& stats.slotId == key.slotId
&& stats.type == key.type
&& stats.successful == key.successful
&& stats.commandCode == key.commandCode
&& stats.networkResponse == key.networkResponse) {
return stats;
}
}
return null;
}
/**
* Returns Presence Notify Event that has the same carrier id, slot id, reason and body in
* response as the given one, or {@code null} if it does not exist.
*/
private @Nullable PresenceNotifyEvent find(PresenceNotifyEvent key) {
for (PresenceNotifyEvent stats : mAtoms.presenceNotifyEvent) {
if (stats.carrierId == key.carrierId
&& stats.slotId == key.slotId
&& stats.reason == key.reason
&& stats.contentBodyReceived == key.contentBodyReceived) {
return stats;
}
}
return null;
}
/**
* Returns GBA Event that has the same carrier id, slot id, result of operation and fail reason
* as the given one, or {@code null} if it does not exist.
*/
private @Nullable GbaEvent find(GbaEvent key) {
for (GbaEvent stats : mAtoms.gbaEvent) {
if (stats.carrierId == key.carrierId
&& stats.slotId == key.slotId
&& stats.successful == key.successful
&& stats.failedReason == key.failedReason) {
return stats;
}
}
return null;
}
/**
* Returns Sip Transport Feature Tag Stats that has the same carrier id, slot id, feature tag
* name, deregister reason, denied reason and feature tag name or custom feature tag name as
* the given one, or {@code null} if it does not exist.
*/
private @Nullable SipTransportFeatureTagStats find(SipTransportFeatureTagStats key) {
for (SipTransportFeatureTagStats stat : mAtoms.sipTransportFeatureTagStats) {
if (stat.carrierId == key.carrierId
&& stat.slotId == key.slotId
&& stat.featureTagName == key.featureTagName
&& stat.sipTransportDeregisteredReason == key.sipTransportDeregisteredReason
&& stat.sipTransportDeniedReason == key.sipTransportDeniedReason) {
return stat;
}
}
return null;
}
/** Returns the UnmeteredNetworks given a phone id. */
private @Nullable UnmeteredNetworks findUnmeteredNetworks(int phoneId) {
for (UnmeteredNetworks unmeteredNetworks : mAtoms.unmeteredNetworks) {
if (unmeteredNetworks.phoneId == phoneId) {
return unmeteredNetworks;
}
}
return null;
}
/**
* Inserts a new element in a random position in an array with a maximum size.
*
* <p>If the array is full, merge with existing item if possible or replace one item randomly.
*/
private static <T> T[] insertAtRandomPlace(T[] storage, T instance, int maxLength) {
final int newLength = storage.length + 1;
final boolean arrayFull = (newLength > maxLength);
T[] result = Arrays.copyOf(storage, arrayFull ? maxLength : newLength);
if (newLength == 1) {
result[0] = instance;
} else if (arrayFull) {
if (instance instanceof OutgoingSms || instance instanceof IncomingSms) {
mergeSmsOrEvictInFullStorage(result, instance);
} else {
result[findItemToEvict(storage)] = instance;
}
} else {
// insert at random place (by moving the item at the random place to the end)
int insertAt = sRandom.nextInt(newLength);
result[newLength - 1] = result[insertAt];
result[insertAt] = instance;
}
return result;
}
/**
* Merge new sms in a full storage.
*
* <p>If new sms is similar to old sms, merge them.
* If not, merge 2 old similar sms and add the new sms.
* If not, replace old sms with the lowest count.
*/
private static <T> void mergeSmsOrEvictInFullStorage(T[] storage, T instance) {
// key: hashCode, value: smsIndex
SparseIntArray map = new SparseIntArray();
int smsIndex1 = -1;
int smsIndex2 = -1;
int indexLowestCount = -1;
int minCount = Integer.MAX_VALUE;
for (int i = 0; i < storage.length; i++) {
// If the new SMS can be merged to an existing item, merge it and return immediately.
if (areSmsMergeable(storage[i], instance)) {
storage[i] = mergeSms(storage[i], instance);
return;
}
// Keep sms index with lowest count to evict, in case we cannot merge any 2 messages.
int smsCount = getSmsCount(storage[i]);
if (smsCount < minCount) {
indexLowestCount = i;
minCount = smsCount;
}
// Find any 2 messages in the storage that can be merged together.
if (smsIndex1 != -1) {
int smsHashCode = getSmsHashCode(storage[i]);
if (map.indexOfKey(smsHashCode) < 0) {
map.append(smsHashCode, i);
} else {
smsIndex1 = map.get(smsHashCode);
smsIndex2 = i;
}
}
}
// Merge 2 similar old sms and add the new sms
if (smsIndex1 != -1) {
storage[smsIndex1] = mergeSms(storage[smsIndex1], storage[smsIndex2]);
storage[smsIndex2] = instance;
return;
}
// Or replace old sms that has the lowest count
storage[indexLowestCount] = instance;
return;
}
private static <T> int getSmsHashCode(T sms) {
return sms instanceof OutgoingSms
? ((OutgoingSms) sms).hashCode : ((IncomingSms) sms).hashCode;
}
private static <T> int getSmsCount(T sms) {
return sms instanceof OutgoingSms
? ((OutgoingSms) sms).count : ((IncomingSms) sms).count;
}
/** Compares 2 SMS hash codes to check if they can be clubbed together in the metrics. */
private static <T> boolean areSmsMergeable(T instance1, T instance2) {
return getSmsHashCode(instance1) == getSmsHashCode(instance2);
}
/** Merges sms2 data on top of sms1 and returns the merged value. */
private static <T> T mergeSms(T sms1, T sms2) {
if (sms1 instanceof OutgoingSms) {
OutgoingSms tSms1 = (OutgoingSms) sms1;
OutgoingSms tSms2 = (OutgoingSms) sms2;
tSms1.intervalMillis = (tSms1.intervalMillis * tSms1.count
+ tSms2.intervalMillis * tSms2.count) / (tSms1.count + tSms2.count);
tSms1.count += tSms2.count;
} else if (sms1 instanceof IncomingSms) {
IncomingSms tSms1 = (IncomingSms) sms1;
IncomingSms tSms2 = (IncomingSms) sms2;
tSms1.count += tSms2.count;
}
return sms1;
}
/** Returns index of the item suitable for eviction when the array is full. */
private static <T> int findItemToEvict(T[] array) {
if (array instanceof CellularServiceState[]) {
// Evict the item that was used least recently
CellularServiceState[] arr = (CellularServiceState[]) array;
return IntStream.range(0, arr.length)
.reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
.getAsInt();
}
if (array instanceof CellularDataServiceSwitch[]) {
// Evict the item that was used least recently
CellularDataServiceSwitch[] arr = (CellularDataServiceSwitch[]) array;
return IntStream.range(0, arr.length)
.reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
.getAsInt();
}
if (array instanceof ImsRegistrationStats[]) {
// Evict the item that was used least recently
ImsRegistrationStats[] arr = (ImsRegistrationStats[]) array;
return IntStream.range(0, arr.length)
.reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
.getAsInt();
}
if (array instanceof ImsRegistrationTermination[]) {
// Evict the item that was used least recently
ImsRegistrationTermination[] arr = (ImsRegistrationTermination[]) array;
return IntStream.range(0, arr.length)
.reduce((i, j) -> arr[i].lastUsedMillis < arr[j].lastUsedMillis ? i : j)
.getAsInt();
}
if (array instanceof VoiceCallSession[]) {
// For voice calls, try to keep emergency calls over regular calls.
VoiceCallSession[] arr = (VoiceCallSession[]) array;
int[] nonEmergencyCallIndexes = IntStream.range(0, arr.length)
.filter(i -> !arr[i].isEmergency)
.toArray();
if (nonEmergencyCallIndexes.length > 0) {
return nonEmergencyCallIndexes[sRandom.nextInt(nonEmergencyCallIndexes.length)];
}
// If all calls in the storage are emergency calls, proceed with default case
// even if the new call is not an emergency call.
}
return sRandom.nextInt(array.length);
}
/** Sanitizes the loaded array of atoms to avoid null values. */
private <T> T[] sanitizeAtoms(T[] array, Class<T> cl) {
return ArrayUtils.emptyIfNull(array, cl);
}
/** Sanitizes the loaded array of atoms loaded to avoid null values and enforce max length. */
private <T> T[] sanitizeAtoms(T[] array, Class<T> cl, int maxLength) {
array = sanitizeAtoms(array, cl);
if (array.length > maxLength) {
return Arrays.copyOf(array, maxLength);
}
return array;
}
/** Sanitizes the timestamp of the last pull loaded from persistent storage. */
private long sanitizeTimestamp(long timestamp) {
return timestamp <= 0L ? getWallTimeMillis() : timestamp;
}
/**
* Returns {@link ImsRegistrationStats} array with durations normalized to 24 hours
* depending on the interval.
*/
private ImsRegistrationStats[] normalizeData(ImsRegistrationStats[] stats,
long intervalMillis) {
for (int i = 0; i < stats.length; i++) {
stats[i].registeredMillis =
normalizeDurationTo24H(stats[i].registeredMillis, intervalMillis);
stats[i].voiceCapableMillis =
normalizeDurationTo24H(stats[i].voiceCapableMillis, intervalMillis);
stats[i].voiceAvailableMillis =
normalizeDurationTo24H(stats[i].voiceAvailableMillis, intervalMillis);
stats[i].smsCapableMillis =
normalizeDurationTo24H(stats[i].smsCapableMillis, intervalMillis);
stats[i].smsAvailableMillis =
normalizeDurationTo24H(stats[i].smsAvailableMillis, intervalMillis);
stats[i].videoCapableMillis =
normalizeDurationTo24H(stats[i].videoCapableMillis, intervalMillis);
stats[i].videoAvailableMillis =
normalizeDurationTo24H(stats[i].videoAvailableMillis, intervalMillis);
stats[i].utCapableMillis =
normalizeDurationTo24H(stats[i].utCapableMillis, intervalMillis);
stats[i].utAvailableMillis =
normalizeDurationTo24H(stats[i].utAvailableMillis, intervalMillis);
}
return stats;
}
/** Returns a duration normalized to 24 hours. */
private long normalizeDurationTo24H(long timeInMillis, long intervalMillis) {
long interval = intervalMillis < 1000 ? 1 : intervalMillis / 1000;
return ((timeInMillis / 1000) * (DAY_IN_MILLIS / 1000) / interval) * 1000;
}
/** Returns an empty PersistAtoms with pull timestamp set to current time. */
private PersistAtoms makeNewPersistAtoms() {
PersistAtoms atoms = new PersistAtoms();
// allow pulling only after some time so data are sufficiently aggregated
long currentTime = getWallTimeMillis();
atoms.buildFingerprint = Build.FINGERPRINT;
atoms.voiceCallRatUsagePullTimestampMillis = currentTime;
atoms.voiceCallSessionPullTimestampMillis = currentTime;
atoms.incomingSmsPullTimestampMillis = currentTime;
atoms.outgoingSmsPullTimestampMillis = currentTime;
atoms.carrierIdTableVersion = TelephonyManager.UNKNOWN_CARRIER_ID_LIST_VERSION;
atoms.dataCallSessionPullTimestampMillis = currentTime;
atoms.cellularServiceStatePullTimestampMillis = currentTime;
atoms.cellularDataServiceSwitchPullTimestampMillis = currentTime;
atoms.imsRegistrationStatsPullTimestampMillis = currentTime;
atoms.imsRegistrationTerminationPullTimestampMillis = currentTime;
atoms.networkRequestsPullTimestampMillis = currentTime;
atoms.networkRequestsV2PullTimestampMillis = currentTime;
atoms.imsRegistrationFeatureTagStatsPullTimestampMillis = currentTime;
atoms.rcsClientProvisioningStatsPullTimestampMillis = currentTime;
atoms.rcsAcsProvisioningStatsPullTimestampMillis = currentTime;
atoms.sipDelegateStatsPullTimestampMillis = currentTime;
atoms.sipTransportFeatureTagStatsPullTimestampMillis = currentTime;
atoms.sipMessageResponsePullTimestampMillis = currentTime;
atoms.sipTransportSessionPullTimestampMillis = currentTime;
atoms.imsDedicatedBearerListenerEventPullTimestampMillis = currentTime;
atoms.imsDedicatedBearerEventPullTimestampMillis = currentTime;
atoms.imsRegistrationServiceDescStatsPullTimestampMillis = currentTime;
atoms.uceEventStatsPullTimestampMillis = currentTime;
atoms.presenceNotifyEventPullTimestampMillis = currentTime;
atoms.gbaEventPullTimestampMillis = currentTime;
Rlog.d(TAG, "created new PersistAtoms");
return atoms;
}
@VisibleForTesting
protected long getWallTimeMillis() {
// Epoch time in UTC, preserved across reboots, but can be adjusted e.g. by the user or NTP
return System.currentTimeMillis();
}
}