blob: 9da1d2b960a6d41580847374d3db6f49cbbe724b [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.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ID_TABLE_VERSION;
import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH;
import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE;
import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION;
import static com.android.internal.telephony.TelephonyStatsLog.DEVICE_TELEPHONY_PROPERTIES;
import static com.android.internal.telephony.TelephonyStatsLog.GBA_EVENT;
import static com.android.internal.telephony.TelephonyStatsLog.IMS_DEDICATED_BEARER_EVENT;
import static com.android.internal.telephony.TelephonyStatsLog.IMS_DEDICATED_BEARER_LISTENER_EVENT;
import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_FEATURE_TAG_STATS;
import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_SERVICE_DESC_STATS;
import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_STATS;
import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_TERMINATION;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS;
import static com.android.internal.telephony.TelephonyStatsLog.PER_SIM_STATUS;
import static com.android.internal.telephony.TelephonyStatsLog.PRESENCE_NOTIFY_EVENT;
import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS;
import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS;
import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE;
import static com.android.internal.telephony.TelephonyStatsLog.SIP_DELEGATE_STATS;
import static com.android.internal.telephony.TelephonyStatsLog.SIP_MESSAGE_RESPONSE;
import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_FEATURE_TAG_STATS;
import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_SESSION;
import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
import static com.android.internal.telephony.TelephonyStatsLog.TELEPHONY_NETWORK_REQUESTS_V2;
import static com.android.internal.telephony.TelephonyStatsLog.UCE_EVENT_STATS;
import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION;
import android.annotation.Nullable;
import android.app.StatsManager;
import android.content.Context;
import android.util.StatsEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.TelephonyStatsLog;
import com.android.internal.telephony.imsphone.ImsPhone;
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.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.VoiceCallRatUsage;
import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
import com.android.internal.util.ConcurrentUtils;
import com.android.telephony.Rlog;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Implements statsd pullers for Telephony.
*
* <p>This class registers pullers to statsd, which will be called once a day to obtain telephony
* statistics that cannot be sent to statsd in real time.
*/
public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
private static final String TAG = MetricsCollector.class.getSimpleName();
/** Disables various restrictions to ease debugging during development. */
private static final boolean DBG = false; // STOPSHIP if true
/**
* Sets atom pull cool down to 23 hours to help enforcing privacy requirement.
*
* <p>Applies to certain atoms. The interval of 23 hours leaves some margin for pull operations
* that occur once a day.
*/
private static final long MIN_COOLDOWN_MILLIS =
DBG ? 10L * SECOND_IN_MILLIS : 23L * HOUR_IN_MILLIS;
/**
* Buckets with less than these many calls will be dropped.
*
* <p>Applies to metrics with duration fields. Currently used by voice call RAT usages.
*/
private static final long MIN_CALLS_PER_BUCKET = DBG ? 0L : 5L;
/** Bucket size in milliseconds to round call durations into. */
private static final long DURATION_BUCKET_MILLIS =
DBG ? 2L * SECOND_IN_MILLIS : 5L * MINUTE_IN_MILLIS;
private static final StatsManager.PullAtomMetadata POLICY_PULL_DAILY =
new StatsManager.PullAtomMetadata.Builder()
.setCoolDownMillis(MIN_COOLDOWN_MILLIS)
.build();
private final PersistAtomsStorage mStorage;
private final StatsManager mStatsManager;
private final AirplaneModeStats mAirplaneModeStats;
private final Set<DataCallSessionStats> mOngoingDataCallStats = ConcurrentHashMap.newKeySet();
private static final Random sRandom = new Random();
public MetricsCollector(Context context) {
this(context, new PersistAtomsStorage(context));
}
/** Allows dependency injection. Used during unit tests. */
@VisibleForTesting
public MetricsCollector(Context context,
PersistAtomsStorage storage) {
mStorage = storage;
mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER);
if (mStatsManager != null) {
registerAtom(CELLULAR_DATA_SERVICE_SWITCH, POLICY_PULL_DAILY);
registerAtom(CELLULAR_SERVICE_STATE, POLICY_PULL_DAILY);
registerAtom(SIM_SLOT_STATE, null);
registerAtom(SUPPORTED_RADIO_ACCESS_FAMILY, null);
registerAtom(VOICE_CALL_RAT_USAGE, POLICY_PULL_DAILY);
registerAtom(VOICE_CALL_SESSION, POLICY_PULL_DAILY);
registerAtom(INCOMING_SMS, POLICY_PULL_DAILY);
registerAtom(OUTGOING_SMS, POLICY_PULL_DAILY);
registerAtom(CARRIER_ID_TABLE_VERSION, null);
registerAtom(DATA_CALL_SESSION, POLICY_PULL_DAILY);
registerAtom(IMS_REGISTRATION_STATS, POLICY_PULL_DAILY);
registerAtom(IMS_REGISTRATION_TERMINATION, POLICY_PULL_DAILY);
registerAtom(TELEPHONY_NETWORK_REQUESTS_V2, POLICY_PULL_DAILY);
registerAtom(IMS_REGISTRATION_FEATURE_TAG_STATS, POLICY_PULL_DAILY);
registerAtom(RCS_CLIENT_PROVISIONING_STATS, POLICY_PULL_DAILY);
registerAtom(RCS_ACS_PROVISIONING_STATS, POLICY_PULL_DAILY);
registerAtom(SIP_DELEGATE_STATS, POLICY_PULL_DAILY);
registerAtom(SIP_TRANSPORT_FEATURE_TAG_STATS, POLICY_PULL_DAILY);
registerAtom(SIP_MESSAGE_RESPONSE, POLICY_PULL_DAILY);
registerAtom(SIP_TRANSPORT_SESSION, POLICY_PULL_DAILY);
registerAtom(DEVICE_TELEPHONY_PROPERTIES, null);
registerAtom(IMS_DEDICATED_BEARER_LISTENER_EVENT, POLICY_PULL_DAILY);
registerAtom(IMS_DEDICATED_BEARER_EVENT, POLICY_PULL_DAILY);
registerAtom(IMS_REGISTRATION_SERVICE_DESC_STATS, POLICY_PULL_DAILY);
registerAtom(UCE_EVENT_STATS, POLICY_PULL_DAILY);
registerAtom(PRESENCE_NOTIFY_EVENT, POLICY_PULL_DAILY);
registerAtom(GBA_EVENT, POLICY_PULL_DAILY);
registerAtom(PER_SIM_STATUS, null);
Rlog.d(TAG, "registered");
} else {
Rlog.e(TAG, "could not get StatsManager, atoms not registered");
}
mAirplaneModeStats = new AirplaneModeStats(context);
}
/**
* {@inheritDoc}
*
* @return {@link StatsManager#PULL_SUCCESS} with list of atoms (potentially empty) if pull
* succeeded, {@link StatsManager#PULL_SKIP} if pull was too frequent or atom ID is
* unexpected.
*/
@Override
public int onPullAtom(int atomTag, List<StatsEvent> data) {
switch (atomTag) {
case CELLULAR_DATA_SERVICE_SWITCH:
return pullCellularDataServiceSwitch(data);
case CELLULAR_SERVICE_STATE:
return pullCellularServiceState(data);
case SIM_SLOT_STATE:
return pullSimSlotState(data);
case SUPPORTED_RADIO_ACCESS_FAMILY:
return pullSupportedRadioAccessFamily(data);
case VOICE_CALL_RAT_USAGE:
return pullVoiceCallRatUsages(data);
case VOICE_CALL_SESSION:
return pullVoiceCallSessions(data);
case INCOMING_SMS:
return pullIncomingSms(data);
case OUTGOING_SMS:
return pullOutgoingSms(data);
case CARRIER_ID_TABLE_VERSION:
return pullCarrierIdTableVersion(data);
case DATA_CALL_SESSION:
return pullDataCallSession(data);
case IMS_REGISTRATION_STATS:
return pullImsRegistrationStats(data);
case IMS_REGISTRATION_TERMINATION:
return pullImsRegistrationTermination(data);
case TELEPHONY_NETWORK_REQUESTS_V2:
return pullTelephonyNetworkRequestsV2(data);
case DEVICE_TELEPHONY_PROPERTIES:
return pullDeviceTelephonyProperties(data);
case IMS_REGISTRATION_FEATURE_TAG_STATS:
return pullImsRegistrationFeatureTagStats(data);
case RCS_CLIENT_PROVISIONING_STATS:
return pullRcsClientProvisioningStats(data);
case RCS_ACS_PROVISIONING_STATS:
return pullRcsAcsProvisioningStats(data);
case SIP_DELEGATE_STATS:
return pullSipDelegateStats(data);
case SIP_TRANSPORT_FEATURE_TAG_STATS:
return pullSipTransportFeatureTagStats(data);
case SIP_MESSAGE_RESPONSE:
return pullSipMessageResponse(data);
case SIP_TRANSPORT_SESSION:
return pullSipTransportSession(data);
case IMS_DEDICATED_BEARER_LISTENER_EVENT:
return pullImsDedicatedBearerListenerEvent(data);
case IMS_DEDICATED_BEARER_EVENT:
return pullImsDedicatedBearerEvent(data);
case IMS_REGISTRATION_SERVICE_DESC_STATS:
return pullImsRegistrationServiceDescStats(data);
case UCE_EVENT_STATS:
return pullUceEventStats(data);
case PRESENCE_NOTIFY_EVENT:
return pullPresenceNotifyEvent(data);
case GBA_EVENT:
return pullGbaEvent(data);
case PER_SIM_STATUS:
return pullPerSimStatus(data);
default:
Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
return StatsManager.PULL_SKIP;
}
}
/** Returns the {@link PersistAtomsStorage} backing the puller. */
public PersistAtomsStorage getAtomsStorage() {
return mStorage;
}
/**
* Registers a {@link DataCallSessionStats} which will be pinged for on-going data calls when
* data call atoms are pulled.
*/
public void registerOngoingDataCallStat(DataCallSessionStats call) {
mOngoingDataCallStats.add(call);
}
/** Unregisters a {@link DataCallSessionStats} when it no longer handles an active data call. */
public void unregisterOngoingDataCallStat(DataCallSessionStats call) {
mOngoingDataCallStats.remove(call);
}
private static int pullSimSlotState(List<StatsEvent> data) {
SimSlotState state;
try {
state = SimSlotState.getCurrentState();
} catch (RuntimeException e) {
// UiccController has not been made yet
return StatsManager.PULL_SKIP;
}
data.add(
TelephonyStatsLog.buildStatsEvent(
SIM_SLOT_STATE,
state.numActiveSlots,
state.numActiveSims,
state.numActiveEsims));
return StatsManager.PULL_SUCCESS;
}
private static int pullSupportedRadioAccessFamily(List<StatsEvent> data) {
Phone[] phones = getPhonesIfAny();
if (phones.length == 0) {
return StatsManager.PULL_SKIP;
}
// The bitmask is defined in android.telephony.TelephonyManager.NetworkTypeBitMask
long rafSupported = 0L;
for (Phone phone : PhoneFactory.getPhones()) {
rafSupported |= phone.getRadioAccessFamily();
}
data.add(TelephonyStatsLog.buildStatsEvent(SUPPORTED_RADIO_ACCESS_FAMILY, rafSupported));
return StatsManager.PULL_SUCCESS;
}
private static int pullCarrierIdTableVersion(List<StatsEvent> data) {
Phone[] phones = getPhonesIfAny();
if (phones.length == 0) {
return StatsManager.PULL_SKIP;
} else {
// All phones should have the same version of the carrier ID table, so only query the
// first one.
int version = phones[0].getCarrierIdListVersion();
data.add(TelephonyStatsLog.buildStatsEvent(CARRIER_ID_TABLE_VERSION, version));
return StatsManager.PULL_SUCCESS;
}
}
private int pullVoiceCallRatUsages(List<StatsEvent> data) {
VoiceCallRatUsage[] usages = mStorage.getVoiceCallRatUsages(MIN_COOLDOWN_MILLIS);
if (usages != null) {
// sort by carrier/RAT and remove buckets with insufficient number of calls
Arrays.stream(usages)
.sorted(
Comparator.comparingLong(
usage -> ((long) usage.carrierId << 32) | usage.rat))
.filter(usage -> usage.callCount >= MIN_CALLS_PER_BUCKET)
.forEach(usage -> data.add(buildStatsEvent(usage)));
Rlog.d(
TAG,
String.format(
"%d out of %d VOICE_CALL_RAT_USAGE pulled",
data.size(), usages.length));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "VOICE_CALL_RAT_USAGE pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullVoiceCallSessions(List<StatsEvent> data) {
VoiceCallSession[] calls = mStorage.getVoiceCallSessions(MIN_COOLDOWN_MILLIS);
if (calls != null) {
// call session list is already shuffled when calls were inserted
Arrays.stream(calls).forEach(call -> data.add(buildStatsEvent(call)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "VOICE_CALL_SESSION pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullIncomingSms(List<StatsEvent> data) {
IncomingSms[] smsList = mStorage.getIncomingSms(MIN_COOLDOWN_MILLIS);
if (smsList != null) {
// SMS list is already shuffled when SMS were inserted
Arrays.stream(smsList).forEach(sms -> data.add(buildStatsEvent(sms)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "INCOMING_SMS pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullOutgoingSms(List<StatsEvent> data) {
OutgoingSms[] smsList = mStorage.getOutgoingSms(MIN_COOLDOWN_MILLIS);
if (smsList != null) {
// SMS list is already shuffled when SMS were inserted
Arrays.stream(smsList).forEach(sms -> data.add(buildStatsEvent(sms)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "OUTGOING_SMS pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullDataCallSession(List<StatsEvent> data) {
// Include ongoing data call segments
for (DataCallSessionStats stats : mOngoingDataCallStats) {
stats.conclude();
}
DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(MIN_COOLDOWN_MILLIS);
if (dataCallSessions != null) {
Arrays.stream(dataCallSessions)
.forEach(dataCall -> data.add(buildStatsEvent(dataCall)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "DATA_CALL_SESSION pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullCellularDataServiceSwitch(List<StatsEvent> data) {
CellularDataServiceSwitch[] persistAtoms =
mStorage.getCellularDataServiceSwitches(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
// list is already shuffled when instances were inserted
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "CELLULAR_DATA_SERVICE_SWITCH pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullCellularServiceState(List<StatsEvent> data) {
// Include the latest durations
for (Phone phone : getPhonesIfAny()) {
phone.getServiceStateTracker().getServiceStateStats().conclude();
}
CellularServiceState[] persistAtoms =
mStorage.getCellularServiceStates(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
// list is already shuffled when instances were inserted
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "CELLULAR_SERVICE_STATE pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullImsRegistrationStats(List<StatsEvent> data) {
// Include the latest durations
for (Phone phone : getPhonesIfAny()) {
ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
if (imsPhone != null) {
imsPhone.getImsStats().conclude();
}
}
ImsRegistrationStats[] persistAtoms = mStorage.getImsRegistrationStats(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
// list is already shuffled when instances were inserted
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "IMS_REGISTRATION_STATS pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullImsRegistrationTermination(List<StatsEvent> data) {
ImsRegistrationTermination[] persistAtoms =
mStorage.getImsRegistrationTerminations(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
// list is already shuffled when instances were inserted
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "IMS_REGISTRATION_TERMINATION pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullTelephonyNetworkRequestsV2(List<StatsEvent> data) {
NetworkRequestsV2[] persistAtoms = mStorage.getNetworkRequestsV2(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "TELEPHONY_NETWORK_REQUESTS_V2 pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private static int pullDeviceTelephonyProperties(List<StatsEvent> data) {
Phone[] phones = getPhonesIfAny();
if (phones.length == 0) {
return StatsManager.PULL_SKIP;
}
data.add(TelephonyStatsLog.buildStatsEvent(DEVICE_TELEPHONY_PROPERTIES,
phones[0].isUsingNewDataStack()));
return StatsManager.PULL_SUCCESS;
}
private int pullImsRegistrationFeatureTagStats(List<StatsEvent> data) {
RcsStats.getInstance().onFlushIncompleteImsRegistrationFeatureTagStats();
ImsRegistrationFeatureTagStats[] persistAtoms =
mStorage.getImsRegistrationFeatureTagStats(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "IMS_REGISTRATION_FEATURE_TAG_STATS pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullRcsClientProvisioningStats(List<StatsEvent> data) {
RcsClientProvisioningStats[] persistAtoms =
mStorage.getRcsClientProvisioningStats(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "RCS_CLIENT_PROVISIONING_STATS pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullRcsAcsProvisioningStats(List<StatsEvent> data) {
RcsStats.getInstance().onFlushIncompleteRcsAcsProvisioningStats();
RcsAcsProvisioningStats[] persistAtoms =
mStorage.getRcsAcsProvisioningStats(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "RCS_ACS_PROVISIONING_STATS pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullSipDelegateStats(List<StatsEvent> data) {
SipDelegateStats[] persisAtoms =
mStorage.getSipDelegateStats(MIN_COOLDOWN_MILLIS);
if (persisAtoms != null) {
Arrays.stream(persisAtoms)
.forEach(persisAtom -> data.add(buildStatsEvent(persisAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "SIP_DELEGATE_STATS pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullSipTransportFeatureTagStats(List<StatsEvent> data) {
RcsStats.getInstance().concludeSipTransportFeatureTagsStat();
SipTransportFeatureTagStats[] persisAtoms =
mStorage.getSipTransportFeatureTagStats(MIN_COOLDOWN_MILLIS);
if (persisAtoms != null) {
Arrays.stream(persisAtoms)
.forEach(persisAtom -> data.add(buildStatsEvent(persisAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "SIP_DELEGATE_STATS pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullSipMessageResponse(List<StatsEvent> data) {
SipMessageResponse[] persistAtoms =
mStorage.getSipMessageResponse(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "RCS_SIP_MESSAGE_RESPONSE pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullSipTransportSession(List<StatsEvent> data) {
SipTransportSession[] persistAtoms =
mStorage.getSipTransportSession(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "RCS_SIP_TRANSPORT_SESSION pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullImsDedicatedBearerListenerEvent(List<StatsEvent> data) {
ImsDedicatedBearerListenerEvent[] persistAtoms =
mStorage.getImsDedicatedBearerListenerEvent(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "IMS_DEDICATED_BEARER_LISTENER_EVENT pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullImsDedicatedBearerEvent(List<StatsEvent> data) {
ImsDedicatedBearerEvent[] persistAtoms =
mStorage.getImsDedicatedBearerEvent(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "IMS_DEDICATED_BEARER_EVENT pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullImsRegistrationServiceDescStats(List<StatsEvent> data) {
RcsStats.getInstance().onFlushIncompleteImsRegistrationServiceDescStats();
ImsRegistrationServiceDescStats[] persistAtoms =
mStorage.getImsRegistrationServiceDescStats(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "IMS_REGISTRATION_SERVICE_DESC_STATS pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullUceEventStats(List<StatsEvent> data) {
UceEventStats[] persistAtoms = mStorage.getUceEventStats(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "UCE_EVENT_STATS pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullPresenceNotifyEvent(List<StatsEvent> data) {
PresenceNotifyEvent[] persistAtoms = mStorage.getPresenceNotifyEvent(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "PRESENCE_NOTIFY_EVENT pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullGbaEvent(List<StatsEvent> data) {
GbaEvent[] persistAtoms = mStorage.getGbaEvent(MIN_COOLDOWN_MILLIS);
if (persistAtoms != null) {
Arrays.stream(persistAtoms)
.forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
return StatsManager.PULL_SUCCESS;
} else {
Rlog.w(TAG, "GBA_EVENT pull too frequent, skipping");
return StatsManager.PULL_SKIP;
}
}
private int pullPerSimStatus(List<StatsEvent> data) {
int result = StatsManager.PULL_SKIP;
for (Phone phone : getPhonesIfAny()) {
PerSimStatus perSimStatus = PerSimStatus.getCurrentState(phone);
if (perSimStatus == null) {
continue;
}
StatsEvent statsEvent = TelephonyStatsLog.buildStatsEvent(
PER_SIM_STATUS,
phone.getPhoneId(), // simSlotIndex
perSimStatus.carrierId, // carrierId
perSimStatus.phoneNumberSourceUicc, // phoneNumberSourceUicc
perSimStatus.phoneNumberSourceCarrier, // phoneNumberSourceCarrier
perSimStatus.phoneNumberSourceIms, // phoneNumberSourceIms
perSimStatus.advancedCallingSettingEnabled, // volteEnabled
perSimStatus.voWiFiSettingEnabled, // wfcEnabled
perSimStatus.voWiFiModeSetting, // wfcMode
perSimStatus.voWiFiRoamingModeSetting, // wfcRoamingMode
perSimStatus.vtSettingEnabled, // videoCallingEnabled
perSimStatus.dataRoamingEnabled, // dataRoamingEnabled
perSimStatus.preferredNetworkType, // allowedNetworksByUser
perSimStatus.disabled2g, // is2gDisabled
perSimStatus.pin1Enabled, // isPin1Enabled
perSimStatus.minimumVoltageClass, // simVoltageClass
perSimStatus.userModifiedApnTypes, // userModifiedApnTypeBitmask
perSimStatus.unmeteredNetworks); // unmeteredNetworks
data.add(statsEvent);
result = StatsManager.PULL_SUCCESS;
}
return result;
}
/** Registers a pulled atom ID {@code atomId} with optional {@code policy} for pulling. */
private void registerAtom(int atomId, @Nullable StatsManager.PullAtomMetadata policy) {
mStatsManager.setPullAtomCallback(atomId, policy, ConcurrentUtils.DIRECT_EXECUTOR, this);
}
private static StatsEvent buildStatsEvent(CellularDataServiceSwitch serviceSwitch) {
return TelephonyStatsLog.buildStatsEvent(
CELLULAR_DATA_SERVICE_SWITCH,
serviceSwitch.ratFrom,
serviceSwitch.ratTo,
serviceSwitch.simSlotIndex,
serviceSwitch.isMultiSim,
serviceSwitch.carrierId,
serviceSwitch.switchCount);
}
private static StatsEvent buildStatsEvent(CellularServiceState state) {
return TelephonyStatsLog.buildStatsEvent(
CELLULAR_SERVICE_STATE,
state.voiceRat,
state.dataRat,
state.voiceRoamingType,
state.dataRoamingType,
state.isEndc,
state.simSlotIndex,
state.isMultiSim,
state.carrierId,
(int) (round(state.totalTimeMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
state.isEmergencyOnly);
}
private static StatsEvent buildStatsEvent(VoiceCallRatUsage usage) {
return TelephonyStatsLog.buildStatsEvent(
VOICE_CALL_RAT_USAGE,
usage.carrierId,
usage.rat,
round(usage.totalDurationMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS,
usage.callCount);
}
private static StatsEvent buildStatsEvent(VoiceCallSession session) {
return TelephonyStatsLog.buildStatsEvent(
VOICE_CALL_SESSION,
session.bearerAtStart,
session.bearerAtEnd,
session.direction,
session.setupDuration,
session.setupFailed,
session.disconnectReasonCode,
session.disconnectExtraCode,
session.disconnectExtraMessage,
session.ratAtStart,
session.ratAtEnd,
session.ratSwitchCount,
session.codecBitmask,
session.concurrentCallCountAtStart,
session.concurrentCallCountAtEnd,
session.simSlotIndex,
session.isMultiSim,
session.isEsim,
session.carrierId,
session.srvccCompleted,
session.srvccFailureCount,
session.srvccCancellationCount,
session.rttEnabled,
session.isEmergency,
session.isRoaming,
// workaround: dimension required for keeping multiple pulled atoms
sRandom.nextInt(),
// New fields introduced in Android S
session.signalStrengthAtEnd,
session.bandAtEnd,
session.setupDurationMillis,
session.mainCodecQuality,
session.videoEnabled,
session.ratAtConnected,
session.isMultiparty,
session.callDuration);
}
private static StatsEvent buildStatsEvent(IncomingSms sms) {
return TelephonyStatsLog.buildStatsEvent(
INCOMING_SMS,
sms.smsFormat,
sms.smsTech,
sms.rat,
sms.smsType,
sms.totalParts,
sms.receivedParts,
sms.blocked,
sms.error,
sms.isRoaming,
sms.simSlotIndex,
sms.isMultiSim,
sms.isEsim,
sms.carrierId,
sms.messageId,
sms.count);
}
private static StatsEvent buildStatsEvent(OutgoingSms sms) {
return TelephonyStatsLog.buildStatsEvent(
OUTGOING_SMS,
sms.smsFormat,
sms.smsTech,
sms.rat,
sms.sendResult,
sms.errorCode,
sms.isRoaming,
sms.isFromDefaultApp,
sms.simSlotIndex,
sms.isMultiSim,
sms.isEsim,
sms.carrierId,
sms.messageId,
sms.retryId,
sms.intervalMillis,
sms.count);
}
private static StatsEvent buildStatsEvent(DataCallSession dataCallSession) {
return TelephonyStatsLog.buildStatsEvent(
DATA_CALL_SESSION,
dataCallSession.dimension,
dataCallSession.isMultiSim,
dataCallSession.isEsim,
0, // profile is deprecated, so we default to 0
dataCallSession.apnTypeBitmask,
dataCallSession.carrierId,
dataCallSession.isRoaming,
dataCallSession.ratAtEnd,
dataCallSession.oosAtEnd,
dataCallSession.ratSwitchCount,
dataCallSession.isOpportunistic,
dataCallSession.ipType,
dataCallSession.setupFailed,
dataCallSession.failureCause,
dataCallSession.suggestedRetryMillis,
dataCallSession.deactivateReason,
round(dataCallSession.durationMinutes, DURATION_BUCKET_MILLIS / MINUTE_IN_MILLIS),
dataCallSession.ongoing,
dataCallSession.bandAtEnd,
dataCallSession.handoverFailureCauses);
}
private static StatsEvent buildStatsEvent(ImsRegistrationStats stats) {
return TelephonyStatsLog.buildStatsEvent(
IMS_REGISTRATION_STATS,
stats.carrierId,
stats.simSlotIndex,
stats.rat,
(int) (round(stats.registeredMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
(int) (round(stats.voiceCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
(int)
(round(stats.voiceAvailableMillis, DURATION_BUCKET_MILLIS)
/ SECOND_IN_MILLIS),
(int) (round(stats.smsCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
(int) (round(stats.smsAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
(int) (round(stats.videoCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
(int)
(round(stats.videoAvailableMillis, DURATION_BUCKET_MILLIS)
/ SECOND_IN_MILLIS),
(int) (round(stats.utCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
(int) (round(stats.utAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
}
private static StatsEvent buildStatsEvent(ImsRegistrationTermination termination) {
return TelephonyStatsLog.buildStatsEvent(
IMS_REGISTRATION_TERMINATION,
termination.carrierId,
termination.isMultiSim,
termination.ratAtEnd,
termination.setupFailed,
termination.reasonCode,
termination.extraCode,
termination.extraMessage,
termination.count);
}
private static StatsEvent buildStatsEvent(NetworkRequestsV2 networkRequests) {
return TelephonyStatsLog.buildStatsEvent(
TELEPHONY_NETWORK_REQUESTS_V2,
networkRequests.carrierId,
networkRequests.capability,
networkRequests.requestCount);
}
private static StatsEvent buildStatsEvent(ImsRegistrationFeatureTagStats stats) {
return TelephonyStatsLog.buildStatsEvent(
IMS_REGISTRATION_FEATURE_TAG_STATS,
stats.carrierId,
stats.slotId,
stats.featureTagName,
stats.registrationTech,
(int) (round(stats.registeredMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
}
private static StatsEvent buildStatsEvent(RcsClientProvisioningStats stats) {
return TelephonyStatsLog.buildStatsEvent(
RCS_CLIENT_PROVISIONING_STATS,
stats.carrierId,
stats.slotId,
stats.event,
stats.count);
}
private static StatsEvent buildStatsEvent(RcsAcsProvisioningStats stats) {
return TelephonyStatsLog.buildStatsEvent(
RCS_ACS_PROVISIONING_STATS,
stats.carrierId,
stats.slotId,
stats.responseCode,
stats.responseType,
stats.isSingleRegistrationEnabled,
stats.count,
(int) (round(stats.stateTimerMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
}
private static StatsEvent buildStatsEvent(SipDelegateStats stats) {
return TelephonyStatsLog.buildStatsEvent(
SIP_DELEGATE_STATS,
stats.dimension,
stats.carrierId,
stats.slotId,
(int) (round(stats.uptimeMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
stats.destroyReason);
}
private static StatsEvent buildStatsEvent(SipTransportFeatureTagStats stats) {
return TelephonyStatsLog.buildStatsEvent(
SIP_TRANSPORT_FEATURE_TAG_STATS,
stats.carrierId,
stats.slotId,
stats.featureTagName,
stats.sipTransportDeniedReason,
stats.sipTransportDeregisteredReason,
(int) (round(stats.associatedMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
}
private static StatsEvent buildStatsEvent(SipMessageResponse stats) {
return TelephonyStatsLog.buildStatsEvent(
SIP_MESSAGE_RESPONSE,
stats.carrierId,
stats.slotId,
stats.sipMessageMethod,
stats.sipMessageResponse,
stats.sipMessageDirection,
stats.messageError,
stats.count);
}
private static StatsEvent buildStatsEvent(SipTransportSession stats) {
return TelephonyStatsLog.buildStatsEvent(
SIP_TRANSPORT_SESSION,
stats.carrierId,
stats.slotId,
stats.sessionMethod,
stats.sipMessageDirection,
stats.sipResponse,
stats.sessionCount,
stats.endedGracefullyCount);
}
private static StatsEvent buildStatsEvent(ImsDedicatedBearerListenerEvent stats) {
return TelephonyStatsLog.buildStatsEvent(
IMS_DEDICATED_BEARER_LISTENER_EVENT,
stats.carrierId,
stats.slotId,
stats.ratAtEnd,
stats.qci,
stats.dedicatedBearerEstablished,
stats.eventCount);
}
private static StatsEvent buildStatsEvent(ImsDedicatedBearerEvent stats) {
return TelephonyStatsLog.buildStatsEvent(
IMS_DEDICATED_BEARER_EVENT,
stats.carrierId,
stats.slotId,
stats.ratAtEnd,
stats.qci,
stats.bearerState,
stats.localConnectionInfoReceived,
stats.remoteConnectionInfoReceived,
stats.hasListeners,
stats.count);
}
private static StatsEvent buildStatsEvent(ImsRegistrationServiceDescStats stats) {
return TelephonyStatsLog.buildStatsEvent(
IMS_REGISTRATION_SERVICE_DESC_STATS,
stats.carrierId,
stats.slotId,
stats.serviceIdName,
stats.serviceIdVersion,
stats.registrationTech,
(int) (round(stats.publishedMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
}
private static StatsEvent buildStatsEvent(UceEventStats stats) {
return TelephonyStatsLog.buildStatsEvent(
UCE_EVENT_STATS,
stats.carrierId,
stats.slotId,
stats.type,
stats.successful,
stats.commandCode,
stats.networkResponse,
stats.count);
}
private static StatsEvent buildStatsEvent(PresenceNotifyEvent stats) {
return TelephonyStatsLog.buildStatsEvent(
PRESENCE_NOTIFY_EVENT,
stats.carrierId,
stats.slotId,
stats.reason,
stats.contentBodyReceived,
stats.rcsCapsCount,
stats.mmtelCapsCount,
stats.noCapsCount,
stats.count);
}
private static StatsEvent buildStatsEvent(GbaEvent stats) {
return TelephonyStatsLog.buildStatsEvent(
GBA_EVENT,
stats.carrierId,
stats.slotId,
stats.successful,
stats.failedReason,
stats.count);
}
/** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
private static Phone[] getPhonesIfAny() {
try {
return PhoneFactory.getPhones();
} catch (IllegalStateException e) {
// Phones have not been made yet
return new Phone[0];
}
}
/** Returns the value rounded to the bucket. */
private static long round(long value, long bucket) {
return bucket == 0 ? value : ((value + bucket / 2) / bucket) * bucket;
}
}