• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony.metrics;
18 
19 import static android.text.format.DateUtils.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.IMS_REGISTRATION_STATS;
28 import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_TERMINATION;
29 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS;
30 import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS;
31 import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE;
32 import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
33 import static com.android.internal.telephony.TelephonyStatsLog.TELEPHONY_NETWORK_REQUESTS;
34 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
35 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION;
36 
37 import android.annotation.Nullable;
38 import android.app.StatsManager;
39 import android.content.Context;
40 import android.util.StatsEvent;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.telephony.Phone;
44 import com.android.internal.telephony.PhoneFactory;
45 import com.android.internal.telephony.TelephonyStatsLog;
46 import com.android.internal.telephony.imsphone.ImsPhone;
47 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
48 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
49 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
50 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats;
51 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
52 import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
53 import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequests;
54 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
55 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage;
56 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
57 import com.android.internal.util.ConcurrentUtils;
58 import com.android.telephony.Rlog;
59 
60 import java.util.Arrays;
61 import java.util.Comparator;
62 import java.util.List;
63 import java.util.Random;
64 
65 /**
66  * Implements statsd pullers for Telephony.
67  *
68  * <p>This class registers pullers to statsd, which will be called once a day to obtain telephony
69  * statistics that cannot be sent to statsd in real time.
70  */
71 public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
72     private static final String TAG = MetricsCollector.class.getSimpleName();
73 
74     /** Disables various restrictions to ease debugging during development. */
75     private static final boolean DBG = false; // STOPSHIP if true
76 
77     /**
78      * Sets atom pull cool down to 23 hours to help enforcing privacy requirement.
79      *
80      * <p>Applies to certain atoms. The interval of 23 hours leaves some margin for pull operations
81      * that occur once a day.
82      */
83     private static final long MIN_COOLDOWN_MILLIS =
84             DBG ? 10L * SECOND_IN_MILLIS : 23L * HOUR_IN_MILLIS;
85 
86     /**
87      * Buckets with less than these many calls will be dropped.
88      *
89      * <p>Applies to metrics with duration fields. Currently used by voice call RAT usages.
90      */
91     private static final long MIN_CALLS_PER_BUCKET = DBG ? 0L : 5L;
92 
93     /** Bucket size in milliseconds to round call durations into. */
94     private static final long DURATION_BUCKET_MILLIS =
95             DBG ? 2L * SECOND_IN_MILLIS : 5L * MINUTE_IN_MILLIS;
96 
97     private static final StatsManager.PullAtomMetadata POLICY_PULL_DAILY =
98             new StatsManager.PullAtomMetadata.Builder()
99                     .setCoolDownMillis(MIN_COOLDOWN_MILLIS)
100                     .build();
101 
102     private PersistAtomsStorage mStorage;
103     private final StatsManager mStatsManager;
104     private final AirplaneModeStats mAirplaneModeStats;
105     private static final Random sRandom = new Random();
106 
MetricsCollector(Context context)107     public MetricsCollector(Context context) {
108         mStorage = new PersistAtomsStorage(context);
109         mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER);
110         if (mStatsManager != null) {
111             registerAtom(CELLULAR_DATA_SERVICE_SWITCH, POLICY_PULL_DAILY);
112             registerAtom(CELLULAR_SERVICE_STATE, POLICY_PULL_DAILY);
113             registerAtom(SIM_SLOT_STATE, null);
114             registerAtom(SUPPORTED_RADIO_ACCESS_FAMILY, null);
115             registerAtom(VOICE_CALL_RAT_USAGE, POLICY_PULL_DAILY);
116             registerAtom(VOICE_CALL_SESSION, POLICY_PULL_DAILY);
117             registerAtom(INCOMING_SMS, POLICY_PULL_DAILY);
118             registerAtom(OUTGOING_SMS, POLICY_PULL_DAILY);
119             registerAtom(CARRIER_ID_TABLE_VERSION, null);
120             registerAtom(DATA_CALL_SESSION, POLICY_PULL_DAILY);
121             registerAtom(IMS_REGISTRATION_STATS, POLICY_PULL_DAILY);
122             registerAtom(IMS_REGISTRATION_TERMINATION, POLICY_PULL_DAILY);
123             registerAtom(TELEPHONY_NETWORK_REQUESTS, POLICY_PULL_DAILY);
124 
125             Rlog.d(TAG, "registered");
126         } else {
127             Rlog.e(TAG, "could not get StatsManager, atoms not registered");
128         }
129 
130         mAirplaneModeStats = new AirplaneModeStats(context);
131     }
132 
133     /** Replaces the {@link PersistAtomsStorage} backing the puller. Used during unit tests. */
134     @VisibleForTesting
setPersistAtomsStorage(PersistAtomsStorage storage)135     public void setPersistAtomsStorage(PersistAtomsStorage storage) {
136         mStorage = storage;
137     }
138 
139     /**
140      * {@inheritDoc}
141      *
142      * @return {@link StatsManager#PULL_SUCCESS} with list of atoms (potentially empty) if pull
143      *     succeeded, {@link StatsManager#PULL_SKIP} if pull was too frequent or atom ID is
144      *     unexpected.
145      */
146     @Override
onPullAtom(int atomTag, List<StatsEvent> data)147     public int onPullAtom(int atomTag, List<StatsEvent> data) {
148         switch (atomTag) {
149             case CELLULAR_DATA_SERVICE_SWITCH:
150                 return pullCellularDataServiceSwitch(data);
151             case CELLULAR_SERVICE_STATE:
152                 return pullCellularServiceState(data);
153             case SIM_SLOT_STATE:
154                 return pullSimSlotState(data);
155             case SUPPORTED_RADIO_ACCESS_FAMILY:
156                 return pullSupportedRadioAccessFamily(data);
157             case VOICE_CALL_RAT_USAGE:
158                 return pullVoiceCallRatUsages(data);
159             case VOICE_CALL_SESSION:
160                 return pullVoiceCallSessions(data);
161             case INCOMING_SMS:
162                 return pullIncomingSms(data);
163             case OUTGOING_SMS:
164                 return pullOutgoingSms(data);
165             case CARRIER_ID_TABLE_VERSION:
166                 return pullCarrierIdTableVersion(data);
167             case DATA_CALL_SESSION:
168                 return pullDataCallSession(data);
169             case IMS_REGISTRATION_STATS:
170                 return pullImsRegistrationStats(data);
171             case IMS_REGISTRATION_TERMINATION:
172                 return pullImsRegistrationTermination(data);
173             case TELEPHONY_NETWORK_REQUESTS:
174                 return pullTelephonyNetworkRequests(data);
175             default:
176                 Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
177                 return StatsManager.PULL_SKIP;
178         }
179     }
180 
181     /** Returns the {@link PersistAtomsStorage} backing the puller. */
getAtomsStorage()182     public PersistAtomsStorage getAtomsStorage() {
183         return mStorage;
184     }
185 
pullSimSlotState(List<StatsEvent> data)186     private static int pullSimSlotState(List<StatsEvent> data) {
187         SimSlotState state;
188         try {
189             state = SimSlotState.getCurrentState();
190         } catch (RuntimeException e) {
191             // UiccController has not been made yet
192             return StatsManager.PULL_SKIP;
193         }
194 
195         data.add(
196                 TelephonyStatsLog.buildStatsEvent(
197                         SIM_SLOT_STATE,
198                         state.numActiveSlots,
199                         state.numActiveSims,
200                         state.numActiveEsims));
201         return StatsManager.PULL_SUCCESS;
202     }
203 
pullSupportedRadioAccessFamily(List<StatsEvent> data)204     private static int pullSupportedRadioAccessFamily(List<StatsEvent> data) {
205         Phone[] phones = getPhonesIfAny();
206         if (phones.length == 0) {
207             return StatsManager.PULL_SKIP;
208         }
209 
210         // The bitmask is defined in android.telephony.TelephonyManager.NetworkTypeBitMask
211         long rafSupported = 0L;
212         for (Phone phone : PhoneFactory.getPhones()) {
213             rafSupported |= phone.getRadioAccessFamily();
214         }
215 
216         data.add(TelephonyStatsLog.buildStatsEvent(SUPPORTED_RADIO_ACCESS_FAMILY, rafSupported));
217         return StatsManager.PULL_SUCCESS;
218     }
219 
pullCarrierIdTableVersion(List<StatsEvent> data)220     private static int pullCarrierIdTableVersion(List<StatsEvent> data) {
221         Phone[] phones = getPhonesIfAny();
222         if (phones.length == 0) {
223             return StatsManager.PULL_SKIP;
224         } else {
225             // All phones should have the same version of the carrier ID table, so only query the
226             // first one.
227             int version = phones[0].getCarrierIdListVersion();
228             data.add(TelephonyStatsLog.buildStatsEvent(CARRIER_ID_TABLE_VERSION, version));
229             return StatsManager.PULL_SUCCESS;
230         }
231     }
232 
pullVoiceCallRatUsages(List<StatsEvent> data)233     private int pullVoiceCallRatUsages(List<StatsEvent> data) {
234         VoiceCallRatUsage[] usages = mStorage.getVoiceCallRatUsages(MIN_COOLDOWN_MILLIS);
235         if (usages != null) {
236             // sort by carrier/RAT and remove buckets with insufficient number of calls
237             Arrays.stream(usages)
238                     .sorted(
239                             Comparator.comparingLong(
240                                     usage -> ((long) usage.carrierId << 32) | usage.rat))
241                     .filter(usage -> usage.callCount >= MIN_CALLS_PER_BUCKET)
242                     .forEach(usage -> data.add(buildStatsEvent(usage)));
243             Rlog.d(
244                     TAG,
245                     String.format(
246                             "%d out of %d VOICE_CALL_RAT_USAGE pulled",
247                             data.size(), usages.length));
248             return StatsManager.PULL_SUCCESS;
249         } else {
250             Rlog.w(TAG, "VOICE_CALL_RAT_USAGE pull too frequent, skipping");
251             return StatsManager.PULL_SKIP;
252         }
253     }
254 
pullVoiceCallSessions(List<StatsEvent> data)255     private int pullVoiceCallSessions(List<StatsEvent> data) {
256         VoiceCallSession[] calls = mStorage.getVoiceCallSessions(MIN_COOLDOWN_MILLIS);
257         if (calls != null) {
258             // call session list is already shuffled when calls were inserted
259             Arrays.stream(calls).forEach(call -> data.add(buildStatsEvent(call)));
260             return StatsManager.PULL_SUCCESS;
261         } else {
262             Rlog.w(TAG, "VOICE_CALL_SESSION pull too frequent, skipping");
263             return StatsManager.PULL_SKIP;
264         }
265     }
266 
pullIncomingSms(List<StatsEvent> data)267     private int pullIncomingSms(List<StatsEvent> data) {
268         IncomingSms[] smsList = mStorage.getIncomingSms(MIN_COOLDOWN_MILLIS);
269         if (smsList != null) {
270             // SMS list is already shuffled when SMS were inserted
271             Arrays.stream(smsList).forEach(sms -> data.add(buildStatsEvent(sms)));
272             return StatsManager.PULL_SUCCESS;
273         } else {
274             Rlog.w(TAG, "INCOMING_SMS pull too frequent, skipping");
275             return StatsManager.PULL_SKIP;
276         }
277     }
278 
pullOutgoingSms(List<StatsEvent> data)279     private int pullOutgoingSms(List<StatsEvent> data) {
280         OutgoingSms[] smsList = mStorage.getOutgoingSms(MIN_COOLDOWN_MILLIS);
281         if (smsList != null) {
282             // SMS list is already shuffled when SMS were inserted
283             Arrays.stream(smsList).forEach(sms -> data.add(buildStatsEvent(sms)));
284             return StatsManager.PULL_SUCCESS;
285         } else {
286             Rlog.w(TAG, "OUTGOING_SMS pull too frequent, skipping");
287             return StatsManager.PULL_SKIP;
288         }
289     }
290 
pullDataCallSession(List<StatsEvent> data)291     private int pullDataCallSession(List<StatsEvent> data) {
292         DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(MIN_COOLDOWN_MILLIS);
293         if (dataCallSessions != null) {
294             Arrays.stream(dataCallSessions)
295                     .forEach(dataCall -> data.add(buildStatsEvent(dataCall)));
296             return StatsManager.PULL_SUCCESS;
297         } else {
298             Rlog.w(TAG, "DATA_CALL_SESSION pull too frequent, skipping");
299             return StatsManager.PULL_SKIP;
300         }
301     }
302 
pullCellularDataServiceSwitch(List<StatsEvent> data)303     private int pullCellularDataServiceSwitch(List<StatsEvent> data) {
304         CellularDataServiceSwitch[] persistAtoms =
305                 mStorage.getCellularDataServiceSwitches(MIN_COOLDOWN_MILLIS);
306         if (persistAtoms != null) {
307             // list is already shuffled when instances were inserted
308             Arrays.stream(persistAtoms)
309                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
310             return StatsManager.PULL_SUCCESS;
311         } else {
312             Rlog.w(TAG, "CELLULAR_DATA_SERVICE_SWITCH pull too frequent, skipping");
313             return StatsManager.PULL_SKIP;
314         }
315     }
316 
pullCellularServiceState(List<StatsEvent> data)317     private int pullCellularServiceState(List<StatsEvent> data) {
318         // Include the latest durations
319         for (Phone phone : getPhonesIfAny()) {
320             phone.getServiceStateTracker().getServiceStateStats().conclude();
321         }
322 
323         CellularServiceState[] persistAtoms =
324                 mStorage.getCellularServiceStates(MIN_COOLDOWN_MILLIS);
325         if (persistAtoms != null) {
326             // list is already shuffled when instances were inserted
327             Arrays.stream(persistAtoms)
328                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
329             return StatsManager.PULL_SUCCESS;
330         } else {
331             Rlog.w(TAG, "CELLULAR_SERVICE_STATE pull too frequent, skipping");
332             return StatsManager.PULL_SKIP;
333         }
334     }
335 
pullImsRegistrationStats(List<StatsEvent> data)336     private int pullImsRegistrationStats(List<StatsEvent> data) {
337         // Include the latest durations
338         for (Phone phone : getPhonesIfAny()) {
339             ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
340             if (imsPhone != null) {
341                 imsPhone.getImsStats().conclude();
342             }
343         }
344 
345         ImsRegistrationStats[] persistAtoms = mStorage.getImsRegistrationStats(MIN_COOLDOWN_MILLIS);
346         if (persistAtoms != null) {
347             // list is already shuffled when instances were inserted
348             Arrays.stream(persistAtoms)
349                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
350             return StatsManager.PULL_SUCCESS;
351         } else {
352             Rlog.w(TAG, "IMS_REGISTRATION_STATS pull too frequent, skipping");
353             return StatsManager.PULL_SKIP;
354         }
355     }
356 
pullImsRegistrationTermination(List<StatsEvent> data)357     private int pullImsRegistrationTermination(List<StatsEvent> data) {
358         ImsRegistrationTermination[] persistAtoms =
359                 mStorage.getImsRegistrationTerminations(MIN_COOLDOWN_MILLIS);
360         if (persistAtoms != null) {
361             // list is already shuffled when instances were inserted
362             Arrays.stream(persistAtoms)
363                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
364             return StatsManager.PULL_SUCCESS;
365         } else {
366             Rlog.w(TAG, "IMS_REGISTRATION_TERMINATION pull too frequent, skipping");
367             return StatsManager.PULL_SKIP;
368         }
369     }
370 
pullTelephonyNetworkRequests(List<StatsEvent> data)371     private int pullTelephonyNetworkRequests(List<StatsEvent> data) {
372         NetworkRequests[] persistAtoms = mStorage.getNetworkRequests(MIN_COOLDOWN_MILLIS);
373         if (persistAtoms != null) {
374             Arrays.stream(persistAtoms)
375                     .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
376             return StatsManager.PULL_SUCCESS;
377         } else {
378             Rlog.w(TAG, "TELEPHONY_NETWORK_REQUESTS pull too frequent, skipping");
379             return StatsManager.PULL_SKIP;
380         }
381     }
382 
383     /** Registers a pulled atom ID {@code atomId} with optional {@code policy} for pulling. */
registerAtom(int atomId, @Nullable StatsManager.PullAtomMetadata policy)384     private void registerAtom(int atomId, @Nullable StatsManager.PullAtomMetadata policy) {
385         mStatsManager.setPullAtomCallback(atomId, policy, ConcurrentUtils.DIRECT_EXECUTOR, this);
386     }
387 
buildStatsEvent(CellularDataServiceSwitch serviceSwitch)388     private static StatsEvent buildStatsEvent(CellularDataServiceSwitch serviceSwitch) {
389         return TelephonyStatsLog.buildStatsEvent(
390                 CELLULAR_DATA_SERVICE_SWITCH,
391                 serviceSwitch.ratFrom,
392                 serviceSwitch.ratTo,
393                 serviceSwitch.simSlotIndex,
394                 serviceSwitch.isMultiSim,
395                 serviceSwitch.carrierId,
396                 serviceSwitch.switchCount);
397     }
398 
buildStatsEvent(CellularServiceState state)399     private static StatsEvent buildStatsEvent(CellularServiceState state) {
400         return TelephonyStatsLog.buildStatsEvent(
401                 CELLULAR_SERVICE_STATE,
402                 state.voiceRat,
403                 state.dataRat,
404                 state.voiceRoamingType,
405                 state.dataRoamingType,
406                 state.isEndc,
407                 state.simSlotIndex,
408                 state.isMultiSim,
409                 state.carrierId,
410                 (int) (round(state.totalTimeMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
411     }
412 
buildStatsEvent(VoiceCallRatUsage usage)413     private static StatsEvent buildStatsEvent(VoiceCallRatUsage usage) {
414         return TelephonyStatsLog.buildStatsEvent(
415                 VOICE_CALL_RAT_USAGE,
416                 usage.carrierId,
417                 usage.rat,
418                 round(usage.totalDurationMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS,
419                 usage.callCount);
420     }
421 
buildStatsEvent(VoiceCallSession session)422     private static StatsEvent buildStatsEvent(VoiceCallSession session) {
423         return TelephonyStatsLog.buildStatsEvent(
424                 VOICE_CALL_SESSION,
425                 session.bearerAtStart,
426                 session.bearerAtEnd,
427                 session.direction,
428                 session.setupDuration,
429                 session.setupFailed,
430                 session.disconnectReasonCode,
431                 session.disconnectExtraCode,
432                 session.disconnectExtraMessage,
433                 session.ratAtStart,
434                 session.ratAtEnd,
435                 session.ratSwitchCount,
436                 session.codecBitmask,
437                 session.concurrentCallCountAtStart,
438                 session.concurrentCallCountAtEnd,
439                 session.simSlotIndex,
440                 session.isMultiSim,
441                 session.isEsim,
442                 session.carrierId,
443                 session.srvccCompleted,
444                 session.srvccFailureCount,
445                 session.srvccCancellationCount,
446                 session.rttEnabled,
447                 session.isEmergency,
448                 session.isRoaming,
449                 // workaround: dimension required for keeping multiple pulled atoms
450                 sRandom.nextInt(),
451                 // New fields introduced in Android S
452                 session.signalStrengthAtEnd,
453                 session.bandAtEnd,
454                 session.setupDurationMillis,
455                 session.mainCodecQuality,
456                 session.videoEnabled,
457                 session.ratAtConnected,
458                 session.isMultiparty);
459     }
460 
buildStatsEvent(IncomingSms sms)461     private static StatsEvent buildStatsEvent(IncomingSms sms) {
462         return TelephonyStatsLog.buildStatsEvent(
463                 INCOMING_SMS,
464                 sms.smsFormat,
465                 sms.smsTech,
466                 sms.rat,
467                 sms.smsType,
468                 sms.totalParts,
469                 sms.receivedParts,
470                 sms.blocked,
471                 sms.error,
472                 sms.isRoaming,
473                 sms.simSlotIndex,
474                 sms.isMultiSim,
475                 sms.isEsim,
476                 sms.carrierId,
477                 sms.messageId);
478     }
479 
buildStatsEvent(OutgoingSms sms)480     private static StatsEvent buildStatsEvent(OutgoingSms sms) {
481         return TelephonyStatsLog.buildStatsEvent(
482                 OUTGOING_SMS,
483                 sms.smsFormat,
484                 sms.smsTech,
485                 sms.rat,
486                 sms.sendResult,
487                 sms.errorCode,
488                 sms.isRoaming,
489                 sms.isFromDefaultApp,
490                 sms.simSlotIndex,
491                 sms.isMultiSim,
492                 sms.isEsim,
493                 sms.carrierId,
494                 sms.messageId,
495                 sms.retryId);
496     }
497 
buildStatsEvent(DataCallSession dataCallSession)498     private static StatsEvent buildStatsEvent(DataCallSession dataCallSession) {
499         return TelephonyStatsLog.buildStatsEvent(
500                 DATA_CALL_SESSION,
501                 dataCallSession.dimension,
502                 dataCallSession.isMultiSim,
503                 dataCallSession.isEsim,
504                 0, // profile is deprecated, so we default to 0
505                 dataCallSession.apnTypeBitmask,
506                 dataCallSession.carrierId,
507                 dataCallSession.isRoaming,
508                 dataCallSession.ratAtEnd,
509                 dataCallSession.oosAtEnd,
510                 dataCallSession.ratSwitchCount,
511                 dataCallSession.isOpportunistic,
512                 dataCallSession.ipType,
513                 dataCallSession.setupFailed,
514                 dataCallSession.failureCause,
515                 dataCallSession.suggestedRetryMillis,
516                 dataCallSession.deactivateReason,
517                 round(dataCallSession.durationMinutes, DURATION_BUCKET_MILLIS / MINUTE_IN_MILLIS),
518                 dataCallSession.ongoing,
519                 dataCallSession.bandAtEnd);
520     }
521 
buildStatsEvent(ImsRegistrationStats stats)522     private static StatsEvent buildStatsEvent(ImsRegistrationStats stats) {
523         return TelephonyStatsLog.buildStatsEvent(
524                 IMS_REGISTRATION_STATS,
525                 stats.carrierId,
526                 stats.simSlotIndex,
527                 stats.rat,
528                 (int) (round(stats.registeredMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
529                 (int) (round(stats.voiceCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
530                 (int)
531                         (round(stats.voiceAvailableMillis, DURATION_BUCKET_MILLIS)
532                                 / SECOND_IN_MILLIS),
533                 (int) (round(stats.smsCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
534                 (int) (round(stats.smsAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
535                 (int) (round(stats.videoCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
536                 (int)
537                         (round(stats.videoAvailableMillis, DURATION_BUCKET_MILLIS)
538                                 / SECOND_IN_MILLIS),
539                 (int) (round(stats.utCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
540                 (int) (round(stats.utAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
541     }
542 
buildStatsEvent(ImsRegistrationTermination termination)543     private static StatsEvent buildStatsEvent(ImsRegistrationTermination termination) {
544         return TelephonyStatsLog.buildStatsEvent(
545                 IMS_REGISTRATION_TERMINATION,
546                 termination.carrierId,
547                 termination.isMultiSim,
548                 termination.ratAtEnd,
549                 termination.setupFailed,
550                 termination.reasonCode,
551                 termination.extraCode,
552                 termination.extraMessage,
553                 termination.count);
554     }
555 
buildStatsEvent(NetworkRequests networkRequests)556     private static StatsEvent buildStatsEvent(NetworkRequests networkRequests) {
557         return TelephonyStatsLog.buildStatsEvent(
558                 TELEPHONY_NETWORK_REQUESTS,
559                 networkRequests.carrierId,
560                 networkRequests.enterpriseRequestCount,
561                 networkRequests.enterpriseReleaseCount);
562     }
563 
564     /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
getPhonesIfAny()565     private static Phone[] getPhonesIfAny() {
566         try {
567             return PhoneFactory.getPhones();
568         } catch (IllegalStateException e) {
569             // Phones have not been made yet
570             return new Phone[0];
571         }
572     }
573 
574     /** Returns the value rounded to the bucket. */
round(long value, long bucket)575     private static long round(long value, long bucket) {
576         return bucket == 0 ? value : ((value + bucket / 2) / bucket) * bucket;
577     }
578 }
579