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