1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony.metrics;
18 
19 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
20 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
21 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
22 
23 import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ID_TABLE_VERSION;
24 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH;
25 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE;
26 import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION;
27 import static com.android.internal.telephony.TelephonyStatsLog.GBA_EVENT;
28 import static com.android.internal.telephony.TelephonyStatsLog.IMS_DEDICATED_BEARER_EVENT;
29 import static com.android.internal.telephony.TelephonyStatsLog.IMS_DEDICATED_BEARER_LISTENER_EVENT;
30 import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_FEATURE_TAG_STATS;
31 import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_SERVICE_DESC_STATS;
32 import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_STATS;
33 import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_TERMINATION;
34 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS;
35 import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS;
36 import static com.android.internal.telephony.TelephonyStatsLog.PRESENCE_NOTIFY_EVENT;
37 import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS;
38 import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS;
39 import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE;
40 import static com.android.internal.telephony.TelephonyStatsLog.SIP_DELEGATE_STATS;
41 import static com.android.internal.telephony.TelephonyStatsLog.SIP_MESSAGE_RESPONSE;
42 import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_FEATURE_TAG_STATS;
43 import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_SESSION;
44 import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
45 import static com.android.internal.telephony.TelephonyStatsLog.TELEPHONY_NETWORK_REQUESTS;
46 import static com.android.internal.telephony.TelephonyStatsLog.UCE_EVENT_STATS;
47 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
48 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION;
49 
50 import android.annotation.Nullable;
51 import android.app.StatsManager;
52 import android.content.Context;
53 import android.util.StatsEvent;
54 
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.internal.telephony.Phone;
57 import com.android.internal.telephony.PhoneFactory;
58 import com.android.internal.telephony.TelephonyStatsLog;
59 import com.android.internal.telephony.imsphone.ImsPhone;
60 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
61 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
62 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
63 import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent;
64 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent;
65 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent;
66 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationFeatureTagStats;
67 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationServiceDescStats;
68 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats;
69 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
70 import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
71 import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequests;
72 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
73 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
74 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
75 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
76 import com.android.internal.telephony.nano.PersistAtomsProto.SipDelegateStats;
77 import com.android.internal.telephony.nano.PersistAtomsProto.SipMessageResponse;
78 import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportFeatureTagStats;
79 import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportSession;
80 import com.android.internal.telephony.nano.PersistAtomsProto.UceEventStats;
81 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage;
82 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
83 import com.android.internal.util.ConcurrentUtils;
84 import com.android.telephony.Rlog;
85 
86 import java.util.Arrays;
87 import java.util.Comparator;
88 import java.util.List;
89 import java.util.Random;
90 import java.util.Set;
91 import java.util.concurrent.ConcurrentHashMap;
92 
93 /**
94  * Implements statsd pullers for Telephony.
95  *
96  * <p>This class registers pullers to statsd, which will be called once a day to obtain telephony
97  * statistics that cannot be sent to statsd in real time.
98  */
99 public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
100     private static final String TAG = MetricsCollector.class.getSimpleName();
101 
102     /** Disables various restrictions to ease debugging during development. */
103     private static final boolean DBG = false; // STOPSHIP if true
104 
105     /**
106      * Sets atom pull cool down to 23 hours to help enforcing privacy requirement.
107      *
108      * <p>Applies to certain atoms. The interval of 23 hours leaves some margin for pull operations
109      * that occur once a day.
110      */
111     private static final long MIN_COOLDOWN_MILLIS =
112             DBG ? 10L * SECOND_IN_MILLIS : 23L * HOUR_IN_MILLIS;
113 
114     /**
115      * Buckets with less than these many calls will be dropped.
116      *
117      * <p>Applies to metrics with duration fields. Currently used by voice call RAT usages.
118      */
119     private static final long MIN_CALLS_PER_BUCKET = DBG ? 0L : 5L;
120 
121     /** Bucket size in milliseconds to round call durations into. */
122     private static final long DURATION_BUCKET_MILLIS =
123             DBG ? 2L * SECOND_IN_MILLIS : 5L * MINUTE_IN_MILLIS;
124 
125     private static final StatsManager.PullAtomMetadata POLICY_PULL_DAILY =
126             new StatsManager.PullAtomMetadata.Builder()
127                     .setCoolDownMillis(MIN_COOLDOWN_MILLIS)
128                     .build();
129 
130     private PersistAtomsStorage mStorage;
131     private final StatsManager mStatsManager;
132     private final AirplaneModeStats mAirplaneModeStats;
133     private final Set<DataCallSessionStats> mOngoingDataCallStats = ConcurrentHashMap.newKeySet();
134     private static final Random sRandom = new Random();
135 
MetricsCollector(Context context)136     public MetricsCollector(Context context) {
137         mStorage = new PersistAtomsStorage(context);
138         mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER);
139         if (mStatsManager != null) {
140             registerAtom(CELLULAR_DATA_SERVICE_SWITCH, POLICY_PULL_DAILY);
141             registerAtom(CELLULAR_SERVICE_STATE, POLICY_PULL_DAILY);
142             registerAtom(SIM_SLOT_STATE, null);
143             registerAtom(SUPPORTED_RADIO_ACCESS_FAMILY, null);
144             registerAtom(VOICE_CALL_RAT_USAGE, POLICY_PULL_DAILY);
145             registerAtom(VOICE_CALL_SESSION, POLICY_PULL_DAILY);
146             registerAtom(INCOMING_SMS, POLICY_PULL_DAILY);
147             registerAtom(OUTGOING_SMS, POLICY_PULL_DAILY);
148             registerAtom(CARRIER_ID_TABLE_VERSION, null);
149             registerAtom(DATA_CALL_SESSION, POLICY_PULL_DAILY);
150             registerAtom(IMS_REGISTRATION_STATS, POLICY_PULL_DAILY);
151             registerAtom(IMS_REGISTRATION_TERMINATION, POLICY_PULL_DAILY);
152             registerAtom(TELEPHONY_NETWORK_REQUESTS, POLICY_PULL_DAILY);
153             registerAtom(IMS_REGISTRATION_FEATURE_TAG_STATS, POLICY_PULL_DAILY);
154             registerAtom(RCS_CLIENT_PROVISIONING_STATS, POLICY_PULL_DAILY);
155             registerAtom(RCS_ACS_PROVISIONING_STATS, POLICY_PULL_DAILY);
156             registerAtom(SIP_DELEGATE_STATS, POLICY_PULL_DAILY);
157             registerAtom(SIP_TRANSPORT_FEATURE_TAG_STATS, POLICY_PULL_DAILY);
158             registerAtom(SIP_MESSAGE_RESPONSE, POLICY_PULL_DAILY);
159             registerAtom(SIP_TRANSPORT_SESSION, POLICY_PULL_DAILY);
160             registerAtom(IMS_DEDICATED_BEARER_LISTENER_EVENT, POLICY_PULL_DAILY);
161             registerAtom(IMS_DEDICATED_BEARER_EVENT, POLICY_PULL_DAILY);
162             registerAtom(IMS_REGISTRATION_SERVICE_DESC_STATS, POLICY_PULL_DAILY);
163             registerAtom(UCE_EVENT_STATS, POLICY_PULL_DAILY);
164             registerAtom(PRESENCE_NOTIFY_EVENT, POLICY_PULL_DAILY);
165             registerAtom(GBA_EVENT, POLICY_PULL_DAILY);
166 
167             Rlog.d(TAG, "registered");
168         } else {
169             Rlog.e(TAG, "could not get StatsManager, atoms not registered");
170         }
171 
172         mAirplaneModeStats = new AirplaneModeStats(context);
173     }
174 
175     /** Replaces the {@link PersistAtomsStorage} backing the puller. Used during unit tests. */
176     @VisibleForTesting
setPersistAtomsStorage(PersistAtomsStorage storage)177     public void setPersistAtomsStorage(PersistAtomsStorage storage) {
178         mStorage = storage;
179     }
180 
181     /**
182      * {@inheritDoc}
183      *
184      * @return {@link StatsManager#PULL_SUCCESS} with list of atoms (potentially empty) if pull
185      *     succeeded, {@link StatsManager#PULL_SKIP} if pull was too frequent or atom ID is
186      *     unexpected.
187      */
188     @Override
onPullAtom(int atomTag, List<StatsEvent> data)189     public int onPullAtom(int atomTag, List<StatsEvent> data) {
190         switch (atomTag) {
191             case CELLULAR_DATA_SERVICE_SWITCH:
192                 return pullCellularDataServiceSwitch(data);
193             case CELLULAR_SERVICE_STATE:
194                 return pullCellularServiceState(data);
195             case SIM_SLOT_STATE:
196                 return pullSimSlotState(data);
197             case SUPPORTED_RADIO_ACCESS_FAMILY:
198                 return pullSupportedRadioAccessFamily(data);
199             case VOICE_CALL_RAT_USAGE:
200                 return pullVoiceCallRatUsages(data);
201             case VOICE_CALL_SESSION:
202                 return pullVoiceCallSessions(data);
203             case INCOMING_SMS:
204                 return pullIncomingSms(data);
205             case OUTGOING_SMS:
206                 return pullOutgoingSms(data);
207             case CARRIER_ID_TABLE_VERSION:
208                 return pullCarrierIdTableVersion(data);
209             case DATA_CALL_SESSION:
210                 return pullDataCallSession(data);
211             case IMS_REGISTRATION_STATS:
212                 return pullImsRegistrationStats(data);
213             case IMS_REGISTRATION_TERMINATION:
214                 return pullImsRegistrationTermination(data);
215             case TELEPHONY_NETWORK_REQUESTS:
216                 return pullTelephonyNetworkRequests(data);
217             case IMS_REGISTRATION_FEATURE_TAG_STATS:
218                 return pullImsRegistrationFeatureTagStats(data);
219             case RCS_CLIENT_PROVISIONING_STATS:
220                 return pullRcsClientProvisioningStats(data);
221             case RCS_ACS_PROVISIONING_STATS:
222                 return pullRcsAcsProvisioningStats(data);
223             case SIP_DELEGATE_STATS:
224                 return pullSipDelegateStats(data);
225             case SIP_TRANSPORT_FEATURE_TAG_STATS:
226                 return pullSipTransportFeatureTagStats(data);
227             case SIP_MESSAGE_RESPONSE:
228                 return pullSipMessageResponse(data);
229             case SIP_TRANSPORT_SESSION:
230                 return pullSipTransportSession(data);
231             case IMS_DEDICATED_BEARER_LISTENER_EVENT:
232                 return pullImsDedicatedBearerListenerEvent(data);
233             case IMS_DEDICATED_BEARER_EVENT:
234                 return pullImsDedicatedBearerEvent(data);
235             case IMS_REGISTRATION_SERVICE_DESC_STATS:
236                 return pullImsRegistrationServiceDescStats(data);
237             case UCE_EVENT_STATS:
238                 return pullUceEventStats(data);
239             case PRESENCE_NOTIFY_EVENT:
240                 return pullPresenceNotifyEvent(data);
241             case GBA_EVENT:
242                 return pullGbaEvent(data);
243             default:
244                 Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
245                 return StatsManager.PULL_SKIP;
246         }
247     }
248 
249     /** Returns the {@link PersistAtomsStorage} backing the puller. */
getAtomsStorage()250     public PersistAtomsStorage getAtomsStorage() {
251         return mStorage;
252     }
253 
254     /**
255      * Registers a {@link DataCallSessionStats} which will be pinged for on-going data calls when
256      * data call atoms are pulled.
257      */
registerOngoingDataCallStat(DataCallSessionStats call)258     public void registerOngoingDataCallStat(DataCallSessionStats call) {
259         mOngoingDataCallStats.add(call);
260     }
261 
262     /** Unregisters a {@link DataCallSessionStats} when it no longer handles an active data call. */
unregisterOngoingDataCallStat(DataCallSessionStats call)263     public void unregisterOngoingDataCallStat(DataCallSessionStats call) {
264         mOngoingDataCallStats.remove(call);
265     }
266 
pullSimSlotState(List<StatsEvent> data)267     private static int pullSimSlotState(List<StatsEvent> data) {
268         SimSlotState state;
269         try {
270             state = SimSlotState.getCurrentState();
271         } catch (RuntimeException e) {
272             // UiccController has not been made yet
273             return StatsManager.PULL_SKIP;
274         }
275 
276         data.add(
277                 TelephonyStatsLog.buildStatsEvent(
278                         SIM_SLOT_STATE,
279                         state.numActiveSlots,
280                         state.numActiveSims,
281                         state.numActiveEsims));
282         return StatsManager.PULL_SUCCESS;
283     }
284 
pullSupportedRadioAccessFamily(List<StatsEvent> data)285     private static int pullSupportedRadioAccessFamily(List<StatsEvent> data) {
286         Phone[] phones = getPhonesIfAny();
287         if (phones.length == 0) {
288             return StatsManager.PULL_SKIP;
289         }
290 
291         // The bitmask is defined in android.telephony.TelephonyManager.NetworkTypeBitMask
292         long rafSupported = 0L;
293         for (Phone phone : PhoneFactory.getPhones()) {
294             rafSupported |= phone.getRadioAccessFamily();
295         }
296 
297         data.add(TelephonyStatsLog.buildStatsEvent(SUPPORTED_RADIO_ACCESS_FAMILY, rafSupported));
298         return StatsManager.PULL_SUCCESS;
299     }
300 
pullCarrierIdTableVersion(List<StatsEvent> data)301     private static int pullCarrierIdTableVersion(List<StatsEvent> data) {
302         Phone[] phones = getPhonesIfAny();
303         if (phones.length == 0) {
304             return StatsManager.PULL_SKIP;
305         } else {
306             // All phones should have the same version of the carrier ID table, so only query the
307             // first one.
308             int version = phones[0].getCarrierIdListVersion();
309             data.add(TelephonyStatsLog.buildStatsEvent(CARRIER_ID_TABLE_VERSION, version));
310             return StatsManager.PULL_SUCCESS;
311         }
312     }
313 
pullVoiceCallRatUsages(List<StatsEvent> data)314     private int pullVoiceCallRatUsages(List<StatsEvent> data) {
315         VoiceCallRatUsage[] usages = mStorage.getVoiceCallRatUsages(MIN_COOLDOWN_MILLIS);
316         if (usages != null) {
317             // sort by carrier/RAT and remove buckets with insufficient number of calls
318             Arrays.stream(usages)
319                     .sorted(
320                             Comparator.comparingLong(
321                                     usage -> ((long) usage.carrierId << 32) | usage.rat))
322                     .filter(usage -> usage.callCount >= MIN_CALLS_PER_BUCKET)
323                     .forEach(usage -> data.add(buildStatsEvent(usage)));
324             Rlog.d(
325                     TAG,
326                     String.format(
327                             "%d out of %d VOICE_CALL_RAT_USAGE pulled",
328                             data.size(), usages.length));
329             return StatsManager.PULL_SUCCESS;
330         } else {
331             Rlog.w(TAG, "VOICE_CALL_RAT_USAGE pull too frequent, skipping");
332             return StatsManager.PULL_SKIP;
333         }
334     }
335 
pullVoiceCallSessions(List<StatsEvent> data)336     private int pullVoiceCallSessions(List<StatsEvent> data) {
337         VoiceCallSession[] calls = mStorage.getVoiceCallSessions(MIN_COOLDOWN_MILLIS);
338         if (calls != null) {
339             // call session list is already shuffled when calls were inserted
340             Arrays.stream(calls).forEach(call -> data.add(buildStatsEvent(call)));
341             return StatsManager.PULL_SUCCESS;
342         } else {
343             Rlog.w(TAG, "VOICE_CALL_SESSION pull too frequent, skipping");
344             return StatsManager.PULL_SKIP;
345         }
346     }
347 
pullIncomingSms(List<StatsEvent> data)348     private int pullIncomingSms(List<StatsEvent> data) {
349         IncomingSms[] smsList = mStorage.getIncomingSms(MIN_COOLDOWN_MILLIS);
350         if (smsList != null) {
351             // SMS list is already shuffled when SMS were inserted
352             Arrays.stream(smsList).forEach(sms -> data.add(buildStatsEvent(sms)));
353             return StatsManager.PULL_SUCCESS;
354         } else {
355             Rlog.w(TAG, "INCOMING_SMS pull too frequent, skipping");
356             return StatsManager.PULL_SKIP;
357         }
358     }
359 
pullOutgoingSms(List<StatsEvent> data)360     private int pullOutgoingSms(List<StatsEvent> data) {
361         OutgoingSms[] smsList = mStorage.getOutgoingSms(MIN_COOLDOWN_MILLIS);
362         if (smsList != null) {
363             // SMS list is already shuffled when SMS were inserted
364             Arrays.stream(smsList).forEach(sms -> data.add(buildStatsEvent(sms)));
365             return StatsManager.PULL_SUCCESS;
366         } else {
367             Rlog.w(TAG, "OUTGOING_SMS pull too frequent, skipping");
368             return StatsManager.PULL_SKIP;
369         }
370     }
371 
pullDataCallSession(List<StatsEvent> data)372     private int pullDataCallSession(List<StatsEvent> data) {
373         // Include ongoing data call segments
374         for (DataCallSessionStats stats : mOngoingDataCallStats) {
375             stats.conclude();
376         }
377 
378         DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(MIN_COOLDOWN_MILLIS);
379         if (dataCallSessions != null) {
380             Arrays.stream(dataCallSessions)
381                     .forEach(dataCall -> data.add(buildStatsEvent(dataCall)));
382             return StatsManager.PULL_SUCCESS;
383         } else {
384             Rlog.w(TAG, "DATA_CALL_SESSION pull too frequent, skipping");
385             return StatsManager.PULL_SKIP;
386         }
387     }
388 
pullCellularDataServiceSwitch(List<StatsEvent> data)389     private int pullCellularDataServiceSwitch(List<StatsEvent> data) {
390         CellularDataServiceSwitch[] persistAtoms =
391                 mStorage.getCellularDataServiceSwitches(MIN_COOLDOWN_MILLIS);
392         if (persistAtoms != null) {
393             // list is already shuffled when instances were inserted
394             Arrays.stream(persistAtoms)
395                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
396             return StatsManager.PULL_SUCCESS;
397         } else {
398             Rlog.w(TAG, "CELLULAR_DATA_SERVICE_SWITCH pull too frequent, skipping");
399             return StatsManager.PULL_SKIP;
400         }
401     }
402 
pullCellularServiceState(List<StatsEvent> data)403     private int pullCellularServiceState(List<StatsEvent> data) {
404         // Include the latest durations
405         for (Phone phone : getPhonesIfAny()) {
406             phone.getServiceStateTracker().getServiceStateStats().conclude();
407         }
408 
409         CellularServiceState[] persistAtoms =
410                 mStorage.getCellularServiceStates(MIN_COOLDOWN_MILLIS);
411         if (persistAtoms != null) {
412             // list is already shuffled when instances were inserted
413             Arrays.stream(persistAtoms)
414                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
415             return StatsManager.PULL_SUCCESS;
416         } else {
417             Rlog.w(TAG, "CELLULAR_SERVICE_STATE pull too frequent, skipping");
418             return StatsManager.PULL_SKIP;
419         }
420     }
421 
pullImsRegistrationStats(List<StatsEvent> data)422     private int pullImsRegistrationStats(List<StatsEvent> data) {
423         // Include the latest durations
424         for (Phone phone : getPhonesIfAny()) {
425             ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
426             if (imsPhone != null) {
427                 imsPhone.getImsStats().conclude();
428             }
429         }
430 
431         ImsRegistrationStats[] persistAtoms = mStorage.getImsRegistrationStats(MIN_COOLDOWN_MILLIS);
432         if (persistAtoms != null) {
433             // list is already shuffled when instances were inserted
434             Arrays.stream(persistAtoms)
435                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
436             return StatsManager.PULL_SUCCESS;
437         } else {
438             Rlog.w(TAG, "IMS_REGISTRATION_STATS pull too frequent, skipping");
439             return StatsManager.PULL_SKIP;
440         }
441     }
442 
pullImsRegistrationTermination(List<StatsEvent> data)443     private int pullImsRegistrationTermination(List<StatsEvent> data) {
444         ImsRegistrationTermination[] persistAtoms =
445                 mStorage.getImsRegistrationTerminations(MIN_COOLDOWN_MILLIS);
446         if (persistAtoms != null) {
447             // list is already shuffled when instances were inserted
448             Arrays.stream(persistAtoms)
449                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
450             return StatsManager.PULL_SUCCESS;
451         } else {
452             Rlog.w(TAG, "IMS_REGISTRATION_TERMINATION pull too frequent, skipping");
453             return StatsManager.PULL_SKIP;
454         }
455     }
456 
pullTelephonyNetworkRequests(List<StatsEvent> data)457     private int pullTelephonyNetworkRequests(List<StatsEvent> data) {
458         NetworkRequests[] persistAtoms = mStorage.getNetworkRequests(MIN_COOLDOWN_MILLIS);
459         if (persistAtoms != null) {
460             Arrays.stream(persistAtoms)
461                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
462             return StatsManager.PULL_SUCCESS;
463         } else {
464             Rlog.w(TAG, "TELEPHONY_NETWORK_REQUESTS pull too frequent, skipping");
465             return StatsManager.PULL_SKIP;
466         }
467     }
468 
pullImsRegistrationFeatureTagStats(List<StatsEvent> data)469     private int pullImsRegistrationFeatureTagStats(List<StatsEvent> data) {
470         RcsStats.getInstance().onFlushIncompleteImsRegistrationFeatureTagStats();
471 
472         ImsRegistrationFeatureTagStats[] persistAtoms =
473                 mStorage.getImsRegistrationFeatureTagStats(MIN_COOLDOWN_MILLIS);
474         if (persistAtoms != null) {
475             Arrays.stream(persistAtoms)
476                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
477             return StatsManager.PULL_SUCCESS;
478         } else {
479             Rlog.w(TAG, "IMS_REGISTRATION_FEATURE_TAG_STATS pull too frequent, skipping");
480             return StatsManager.PULL_SKIP;
481         }
482     }
483 
pullRcsClientProvisioningStats(List<StatsEvent> data)484     private int pullRcsClientProvisioningStats(List<StatsEvent> data) {
485         RcsClientProvisioningStats[] persistAtoms =
486                 mStorage.getRcsClientProvisioningStats(MIN_COOLDOWN_MILLIS);
487         if (persistAtoms != null) {
488             Arrays.stream(persistAtoms)
489                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
490             return StatsManager.PULL_SUCCESS;
491         } else {
492             Rlog.w(TAG, "RCS_CLIENT_PROVISIONING_STATS pull too frequent, skipping");
493             return StatsManager.PULL_SKIP;
494         }
495     }
496 
pullRcsAcsProvisioningStats(List<StatsEvent> data)497     private int pullRcsAcsProvisioningStats(List<StatsEvent> data) {
498         RcsStats.getInstance().onFlushIncompleteRcsAcsProvisioningStats();
499 
500         RcsAcsProvisioningStats[] persistAtoms =
501                 mStorage.getRcsAcsProvisioningStats(MIN_COOLDOWN_MILLIS);
502         if (persistAtoms != null) {
503             Arrays.stream(persistAtoms)
504                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
505             return StatsManager.PULL_SUCCESS;
506         } else {
507             Rlog.w(TAG, "RCS_ACS_PROVISIONING_STATS pull too frequent, skipping");
508             return StatsManager.PULL_SKIP;
509         }
510     }
511 
pullSipDelegateStats(List<StatsEvent> data)512     private int pullSipDelegateStats(List<StatsEvent> data) {
513         SipDelegateStats[] persisAtoms =
514                 mStorage.getSipDelegateStats(MIN_COOLDOWN_MILLIS);
515         if (persisAtoms != null) {
516             Arrays.stream(persisAtoms)
517                     .forEach(persisAtom -> data.add(buildStatsEvent(persisAtom)));
518             return StatsManager.PULL_SUCCESS;
519         } else {
520             Rlog.w(TAG, "SIP_DELEGATE_STATS pull too frequent, skipping");
521             return StatsManager.PULL_SKIP;
522         }
523     }
524 
pullSipTransportFeatureTagStats(List<StatsEvent> data)525     private int pullSipTransportFeatureTagStats(List<StatsEvent> data) {
526         RcsStats.getInstance().concludeSipTransportFeatureTagsStat();
527 
528         SipTransportFeatureTagStats[] persisAtoms =
529                 mStorage.getSipTransportFeatureTagStats(MIN_COOLDOWN_MILLIS);
530         if (persisAtoms != null) {
531             Arrays.stream(persisAtoms)
532                     .forEach(persisAtom -> data.add(buildStatsEvent(persisAtom)));
533             return StatsManager.PULL_SUCCESS;
534         } else {
535             Rlog.w(TAG, "SIP_DELEGATE_STATS pull too frequent, skipping");
536             return StatsManager.PULL_SKIP;
537         }
538     }
539 
pullSipMessageResponse(List<StatsEvent> data)540     private int pullSipMessageResponse(List<StatsEvent> data) {
541         SipMessageResponse[] persistAtoms =
542                 mStorage.getSipMessageResponse(MIN_COOLDOWN_MILLIS);
543         if (persistAtoms != null) {
544             Arrays.stream(persistAtoms)
545                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
546             return StatsManager.PULL_SUCCESS;
547         } else {
548             Rlog.w(TAG, "RCS_SIP_MESSAGE_RESPONSE pull too frequent, skipping");
549             return StatsManager.PULL_SKIP;
550         }
551     }
552 
pullSipTransportSession(List<StatsEvent> data)553     private int pullSipTransportSession(List<StatsEvent> data) {
554         SipTransportSession[] persistAtoms =
555                 mStorage.getSipTransportSession(MIN_COOLDOWN_MILLIS);
556         if (persistAtoms != null) {
557             Arrays.stream(persistAtoms)
558                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
559             return StatsManager.PULL_SUCCESS;
560         } else {
561             Rlog.w(TAG, "RCS_SIP_TRANSPORT_SESSION pull too frequent, skipping");
562             return StatsManager.PULL_SKIP;
563         }
564     }
565 
pullImsDedicatedBearerListenerEvent(List<StatsEvent> data)566     private int pullImsDedicatedBearerListenerEvent(List<StatsEvent> data) {
567         ImsDedicatedBearerListenerEvent[] persistAtoms =
568             mStorage.getImsDedicatedBearerListenerEvent(MIN_COOLDOWN_MILLIS);
569         if (persistAtoms != null) {
570             Arrays.stream(persistAtoms)
571                 .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
572             return StatsManager.PULL_SUCCESS;
573         } else {
574             Rlog.w(TAG, "IMS_DEDICATED_BEARER_LISTENER_EVENT pull too frequent, skipping");
575             return StatsManager.PULL_SKIP;
576         }
577     }
578 
pullImsDedicatedBearerEvent(List<StatsEvent> data)579     private int pullImsDedicatedBearerEvent(List<StatsEvent> data) {
580         ImsDedicatedBearerEvent[] persistAtoms =
581             mStorage.getImsDedicatedBearerEvent(MIN_COOLDOWN_MILLIS);
582         if (persistAtoms != null) {
583             Arrays.stream(persistAtoms)
584                 .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
585             return StatsManager.PULL_SUCCESS;
586         } else {
587             Rlog.w(TAG, "IMS_DEDICATED_BEARER_EVENT pull too frequent, skipping");
588             return StatsManager.PULL_SKIP;
589         }
590     }
591 
pullImsRegistrationServiceDescStats(List<StatsEvent> data)592     private int pullImsRegistrationServiceDescStats(List<StatsEvent> data) {
593         RcsStats.getInstance().onFlushIncompleteImsRegistrationServiceDescStats();
594         ImsRegistrationServiceDescStats[] persistAtoms =
595             mStorage.getImsRegistrationServiceDescStats(MIN_COOLDOWN_MILLIS);
596         if (persistAtoms != null) {
597             Arrays.stream(persistAtoms)
598                 .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
599             return StatsManager.PULL_SUCCESS;
600         } else {
601             Rlog.w(TAG, "IMS_REGISTRATION_SERVICE_DESC_STATS pull too frequent, skipping");
602             return StatsManager.PULL_SKIP;
603         }
604     }
605 
pullUceEventStats(List<StatsEvent> data)606     private int pullUceEventStats(List<StatsEvent> data) {
607         UceEventStats[] persistAtoms = mStorage.getUceEventStats(MIN_COOLDOWN_MILLIS);
608         if (persistAtoms != null) {
609             Arrays.stream(persistAtoms)
610                 .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
611             return StatsManager.PULL_SUCCESS;
612         } else {
613             Rlog.w(TAG, "UCE_EVENT_STATS pull too frequent, skipping");
614             return StatsManager.PULL_SKIP;
615         }
616     }
617 
pullPresenceNotifyEvent(List<StatsEvent> data)618     private int pullPresenceNotifyEvent(List<StatsEvent> data) {
619         PresenceNotifyEvent[] persistAtoms = mStorage.getPresenceNotifyEvent(MIN_COOLDOWN_MILLIS);
620         if (persistAtoms != null) {
621             Arrays.stream(persistAtoms)
622                 .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
623             return StatsManager.PULL_SUCCESS;
624         } else {
625             Rlog.w(TAG, "PRESENCE_NOTIFY_EVENT pull too frequent, skipping");
626             return StatsManager.PULL_SKIP;
627         }
628     }
629 
pullGbaEvent(List<StatsEvent> data)630     private int pullGbaEvent(List<StatsEvent> data) {
631         GbaEvent[] persistAtoms = mStorage.getGbaEvent(MIN_COOLDOWN_MILLIS);
632         if (persistAtoms != null) {
633             Arrays.stream(persistAtoms)
634                 .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
635             return StatsManager.PULL_SUCCESS;
636         } else {
637             Rlog.w(TAG, "GBA_EVENT pull too frequent, skipping");
638             return StatsManager.PULL_SKIP;
639         }
640     }
641 
642     /** Registers a pulled atom ID {@code atomId} with optional {@code policy} for pulling. */
registerAtom(int atomId, @Nullable StatsManager.PullAtomMetadata policy)643     private void registerAtom(int atomId, @Nullable StatsManager.PullAtomMetadata policy) {
644         mStatsManager.setPullAtomCallback(atomId, policy, ConcurrentUtils.DIRECT_EXECUTOR, this);
645     }
646 
buildStatsEvent(CellularDataServiceSwitch serviceSwitch)647     private static StatsEvent buildStatsEvent(CellularDataServiceSwitch serviceSwitch) {
648         return TelephonyStatsLog.buildStatsEvent(
649                 CELLULAR_DATA_SERVICE_SWITCH,
650                 serviceSwitch.ratFrom,
651                 serviceSwitch.ratTo,
652                 serviceSwitch.simSlotIndex,
653                 serviceSwitch.isMultiSim,
654                 serviceSwitch.carrierId,
655                 serviceSwitch.switchCount);
656     }
657 
buildStatsEvent(CellularServiceState state)658     private static StatsEvent buildStatsEvent(CellularServiceState state) {
659         return TelephonyStatsLog.buildStatsEvent(
660                 CELLULAR_SERVICE_STATE,
661                 state.voiceRat,
662                 state.dataRat,
663                 state.voiceRoamingType,
664                 state.dataRoamingType,
665                 state.isEndc,
666                 state.simSlotIndex,
667                 state.isMultiSim,
668                 state.carrierId,
669                 (int) (round(state.totalTimeMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
670     }
671 
buildStatsEvent(VoiceCallRatUsage usage)672     private static StatsEvent buildStatsEvent(VoiceCallRatUsage usage) {
673         return TelephonyStatsLog.buildStatsEvent(
674                 VOICE_CALL_RAT_USAGE,
675                 usage.carrierId,
676                 usage.rat,
677                 round(usage.totalDurationMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS,
678                 usage.callCount);
679     }
680 
buildStatsEvent(VoiceCallSession session)681     private static StatsEvent buildStatsEvent(VoiceCallSession session) {
682         return TelephonyStatsLog.buildStatsEvent(
683                 VOICE_CALL_SESSION,
684                 session.bearerAtStart,
685                 session.bearerAtEnd,
686                 session.direction,
687                 session.setupDuration,
688                 session.setupFailed,
689                 session.disconnectReasonCode,
690                 session.disconnectExtraCode,
691                 session.disconnectExtraMessage,
692                 session.ratAtStart,
693                 session.ratAtEnd,
694                 session.ratSwitchCount,
695                 session.codecBitmask,
696                 session.concurrentCallCountAtStart,
697                 session.concurrentCallCountAtEnd,
698                 session.simSlotIndex,
699                 session.isMultiSim,
700                 session.isEsim,
701                 session.carrierId,
702                 session.srvccCompleted,
703                 session.srvccFailureCount,
704                 session.srvccCancellationCount,
705                 session.rttEnabled,
706                 session.isEmergency,
707                 session.isRoaming,
708                 // workaround: dimension required for keeping multiple pulled atoms
709                 sRandom.nextInt(),
710                 // New fields introduced in Android S
711                 session.signalStrengthAtEnd,
712                 session.bandAtEnd,
713                 session.setupDurationMillis,
714                 session.mainCodecQuality,
715                 session.videoEnabled,
716                 session.ratAtConnected,
717                 session.isMultiparty);
718     }
719 
buildStatsEvent(IncomingSms sms)720     private static StatsEvent buildStatsEvent(IncomingSms sms) {
721         return TelephonyStatsLog.buildStatsEvent(
722                 INCOMING_SMS,
723                 sms.smsFormat,
724                 sms.smsTech,
725                 sms.rat,
726                 sms.smsType,
727                 sms.totalParts,
728                 sms.receivedParts,
729                 sms.blocked,
730                 sms.error,
731                 sms.isRoaming,
732                 sms.simSlotIndex,
733                 sms.isMultiSim,
734                 sms.isEsim,
735                 sms.carrierId,
736                 sms.messageId);
737     }
738 
buildStatsEvent(OutgoingSms sms)739     private static StatsEvent buildStatsEvent(OutgoingSms sms) {
740         return TelephonyStatsLog.buildStatsEvent(
741                 OUTGOING_SMS,
742                 sms.smsFormat,
743                 sms.smsTech,
744                 sms.rat,
745                 sms.sendResult,
746                 sms.errorCode,
747                 sms.isRoaming,
748                 sms.isFromDefaultApp,
749                 sms.simSlotIndex,
750                 sms.isMultiSim,
751                 sms.isEsim,
752                 sms.carrierId,
753                 sms.messageId,
754                 sms.retryId);
755     }
756 
buildStatsEvent(DataCallSession dataCallSession)757     private static StatsEvent buildStatsEvent(DataCallSession dataCallSession) {
758         return TelephonyStatsLog.buildStatsEvent(
759                 DATA_CALL_SESSION,
760                 dataCallSession.dimension,
761                 dataCallSession.isMultiSim,
762                 dataCallSession.isEsim,
763                 0, // profile is deprecated, so we default to 0
764                 dataCallSession.apnTypeBitmask,
765                 dataCallSession.carrierId,
766                 dataCallSession.isRoaming,
767                 dataCallSession.ratAtEnd,
768                 dataCallSession.oosAtEnd,
769                 dataCallSession.ratSwitchCount,
770                 dataCallSession.isOpportunistic,
771                 dataCallSession.ipType,
772                 dataCallSession.setupFailed,
773                 dataCallSession.failureCause,
774                 dataCallSession.suggestedRetryMillis,
775                 dataCallSession.deactivateReason,
776                 round(dataCallSession.durationMinutes, DURATION_BUCKET_MILLIS / MINUTE_IN_MILLIS),
777                 dataCallSession.ongoing,
778                 dataCallSession.bandAtEnd);
779     }
780 
buildStatsEvent(ImsRegistrationStats stats)781     private static StatsEvent buildStatsEvent(ImsRegistrationStats stats) {
782         return TelephonyStatsLog.buildStatsEvent(
783                 IMS_REGISTRATION_STATS,
784                 stats.carrierId,
785                 stats.simSlotIndex,
786                 stats.rat,
787                 (int) (round(stats.registeredMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
788                 (int) (round(stats.voiceCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
789                 (int)
790                         (round(stats.voiceAvailableMillis, DURATION_BUCKET_MILLIS)
791                                 / SECOND_IN_MILLIS),
792                 (int) (round(stats.smsCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
793                 (int) (round(stats.smsAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
794                 (int) (round(stats.videoCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
795                 (int)
796                         (round(stats.videoAvailableMillis, DURATION_BUCKET_MILLIS)
797                                 / SECOND_IN_MILLIS),
798                 (int) (round(stats.utCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
799                 (int) (round(stats.utAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
800     }
801 
buildStatsEvent(ImsRegistrationTermination termination)802     private static StatsEvent buildStatsEvent(ImsRegistrationTermination termination) {
803         return TelephonyStatsLog.buildStatsEvent(
804                 IMS_REGISTRATION_TERMINATION,
805                 termination.carrierId,
806                 termination.isMultiSim,
807                 termination.ratAtEnd,
808                 termination.setupFailed,
809                 termination.reasonCode,
810                 termination.extraCode,
811                 termination.extraMessage,
812                 termination.count);
813     }
814 
buildStatsEvent(NetworkRequests networkRequests)815     private static StatsEvent buildStatsEvent(NetworkRequests networkRequests) {
816         return TelephonyStatsLog.buildStatsEvent(
817                 TELEPHONY_NETWORK_REQUESTS,
818                 networkRequests.carrierId,
819                 networkRequests.enterpriseRequestCount,
820                 networkRequests.enterpriseReleaseCount);
821     }
822 
buildStatsEvent(ImsRegistrationFeatureTagStats stats)823     private static StatsEvent buildStatsEvent(ImsRegistrationFeatureTagStats stats) {
824         return TelephonyStatsLog.buildStatsEvent(
825                 IMS_REGISTRATION_FEATURE_TAG_STATS,
826                 stats.carrierId,
827                 stats.slotId,
828                 stats.featureTagName,
829                 stats.registrationTech,
830                 (int) (round(stats.registeredMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
831     }
832 
buildStatsEvent(RcsClientProvisioningStats stats)833     private static StatsEvent buildStatsEvent(RcsClientProvisioningStats stats) {
834         return TelephonyStatsLog.buildStatsEvent(
835                 RCS_CLIENT_PROVISIONING_STATS,
836                 stats.carrierId,
837                 stats.slotId,
838                 stats.event,
839                 stats.count);
840     }
841 
buildStatsEvent(RcsAcsProvisioningStats stats)842     private static StatsEvent buildStatsEvent(RcsAcsProvisioningStats stats) {
843         return TelephonyStatsLog.buildStatsEvent(
844                 RCS_ACS_PROVISIONING_STATS,
845                 stats.carrierId,
846                 stats.slotId,
847                 stats.responseCode,
848                 stats.responseType,
849                 stats.isSingleRegistrationEnabled,
850                 stats.count,
851                 (int) (round(stats.stateTimerMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
852     }
853 
buildStatsEvent(SipDelegateStats stats)854     private static StatsEvent buildStatsEvent(SipDelegateStats stats) {
855         return TelephonyStatsLog.buildStatsEvent(
856                 SIP_DELEGATE_STATS,
857                 stats.dimension,
858                 stats.carrierId,
859                 stats.slotId,
860                 (int) (round(stats.uptimeMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
861                 stats.destroyReason);
862     }
863 
buildStatsEvent(SipTransportFeatureTagStats stats)864     private static StatsEvent buildStatsEvent(SipTransportFeatureTagStats stats) {
865         return TelephonyStatsLog.buildStatsEvent(
866                 SIP_TRANSPORT_FEATURE_TAG_STATS,
867                 stats.carrierId,
868                 stats.slotId,
869                 stats.featureTagName,
870                 stats.sipTransportDeniedReason,
871                 stats.sipTransportDeregisteredReason,
872                 (int) (round(stats.associatedMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
873     }
874 
buildStatsEvent(SipMessageResponse stats)875     private static StatsEvent buildStatsEvent(SipMessageResponse stats) {
876         return TelephonyStatsLog.buildStatsEvent(
877                 SIP_MESSAGE_RESPONSE,
878                 stats.carrierId,
879                 stats.slotId,
880                 stats.sipMessageMethod,
881                 stats.sipMessageResponse,
882                 stats.sipMessageDirection,
883                 stats.messageError,
884                 stats.count);
885     }
886 
buildStatsEvent(SipTransportSession stats)887     private static StatsEvent buildStatsEvent(SipTransportSession stats) {
888         return TelephonyStatsLog.buildStatsEvent(
889                 SIP_TRANSPORT_SESSION,
890                 stats.carrierId,
891                 stats.slotId,
892                 stats.sessionMethod,
893                 stats.sipMessageDirection,
894                 stats.sipResponse,
895                 stats.sessionCount,
896                 stats.endedGracefullyCount);
897     }
898 
buildStatsEvent(ImsDedicatedBearerListenerEvent stats)899     private static StatsEvent buildStatsEvent(ImsDedicatedBearerListenerEvent stats) {
900         return TelephonyStatsLog.buildStatsEvent(
901                 IMS_DEDICATED_BEARER_LISTENER_EVENT,
902                 stats.carrierId,
903                 stats.slotId,
904                 stats.ratAtEnd,
905                 stats.qci,
906                 stats.dedicatedBearerEstablished,
907                 stats.eventCount);
908     }
909 
buildStatsEvent(ImsDedicatedBearerEvent stats)910     private static StatsEvent buildStatsEvent(ImsDedicatedBearerEvent stats) {
911         return TelephonyStatsLog.buildStatsEvent(
912                 IMS_DEDICATED_BEARER_EVENT,
913                 stats.carrierId,
914                 stats.slotId,
915                 stats.ratAtEnd,
916                 stats.qci,
917                 stats.bearerState,
918                 stats.localConnectionInfoReceived,
919                 stats.remoteConnectionInfoReceived,
920                 stats.hasListeners,
921                 stats.count);
922     }
923 
buildStatsEvent(ImsRegistrationServiceDescStats stats)924     private static StatsEvent buildStatsEvent(ImsRegistrationServiceDescStats stats) {
925         return TelephonyStatsLog.buildStatsEvent(
926                 IMS_REGISTRATION_SERVICE_DESC_STATS,
927                 stats.carrierId,
928                 stats.slotId,
929                 stats.serviceIdName,
930                 stats.serviceIdVersion,
931                 stats.registrationTech,
932                 (int) (round(stats.publishedMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
933     }
934 
buildStatsEvent(UceEventStats stats)935     private static StatsEvent buildStatsEvent(UceEventStats stats) {
936         return TelephonyStatsLog.buildStatsEvent(
937                 UCE_EVENT_STATS,
938                 stats.carrierId,
939                 stats.slotId,
940                 stats.type,
941                 stats.successful,
942                 stats.commandCode,
943                 stats.networkResponse,
944                 stats.count);
945     }
946 
buildStatsEvent(PresenceNotifyEvent stats)947     private static StatsEvent buildStatsEvent(PresenceNotifyEvent stats) {
948         return TelephonyStatsLog.buildStatsEvent(
949                 PRESENCE_NOTIFY_EVENT,
950                 stats.carrierId,
951                 stats.slotId,
952                 stats.reason,
953                 stats.contentBodyReceived,
954                 stats.rcsCapsCount,
955                 stats.mmtelCapsCount,
956                 stats.noCapsCount,
957                 stats.count);
958     }
959 
buildStatsEvent(GbaEvent stats)960     private static StatsEvent buildStatsEvent(GbaEvent stats) {
961         return TelephonyStatsLog.buildStatsEvent(
962                 GBA_EVENT,
963                 stats.carrierId,
964                 stats.slotId,
965                 stats.successful,
966                 stats.failedReason,
967                 stats.count);
968     }
969 
970     /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
getPhonesIfAny()971     private static Phone[] getPhonesIfAny() {
972         try {
973             return PhoneFactory.getPhones();
974         } catch (IllegalStateException e) {
975             // Phones have not been made yet
976             return new Phone[0];
977         }
978     }
979 
980     /** Returns the value rounded to the bucket. */
round(long value, long bucket)981     private static long round(long value, long bucket) {
982         return bucket == 0 ? value : ((value + bucket / 2) / bucket) * bucket;
983     }
984 }
985