diff options
author | 2024-03-15 10:23:18 +0900 | |
---|---|---|
committer | 2024-03-22 05:34:10 +0900 | |
commit | d048c8bf63ef6cddef5e6e1784d17192e12dd69b (patch) | |
tree | 4352cbd8f3bcec33b29aae7afa35b261320ffb6b | |
parent | 199bedac673b2c351fa95ce6632cc472afea4777 (diff) |
Logs atom for data network validation
When network validation is completed, it is recorded in metrics.
Network validation begins when a request is received from QNS and
a response is recorded as success, failure, not supported, etc.
Bug: 327257377
Test: atest DataNetworkValidationStatsTest PersistAtomsStorageTest
Change-Id: Ib2db3bb340bd3048250111b83bea4526117c5a5a
7 files changed, 786 insertions, 1 deletions
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto index e54c969a7c..fb89155979 100644 --- a/proto/src/persist_atoms.proto +++ b/proto/src/persist_atoms.proto @@ -23,7 +23,7 @@ option java_outer_classname = "PersistAtomsProto"; // Holds atoms to store on persist storage in case of power cycle or process crash. // NOTE: using int64 rather than google.protobuf.Timestamp for timestamps simplifies implementation. -// Next id: 70 +// Next id: 72 message PersistAtoms { /* Aggregated RAT usage during the call. */ repeated VoiceCallRatUsage voice_call_rat_usage = 1; @@ -231,6 +231,12 @@ message PersistAtoms { /* Timestamp of last satellite_sos_message_recommender pull. */ optional int64 satellite_sos_message_recommender_pull_timestamp_millis = 69; + + /* Data Network Validation statistics and information. */ + repeated DataNetworkValidation data_network_validation = 70; + + /* Timestamp of last data_network_validation pull. */ + optional int64 data_network_validation_pull_timestamp_millis = 71; } // The canonical versions of the following enums live in: @@ -694,3 +700,13 @@ message SatelliteSosMessageRecommender { optional int32 recommending_handover_type = 7; optional bool is_satellite_allowed_in_current_location = 8; } + +message DataNetworkValidation { + optional int32 network_type = 1; + optional int32 apn_type_bitmask = 2; + optional int32 signal_strength = 3; + optional int32 validation_result = 4; + optional int64 elapsed_time_in_millis = 5; + optional bool handover_attempted = 6; + optional int32 network_validation_count = 7; +} diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java index bd98403a41..a718b903f8 100644 --- a/src/java/com/android/internal/telephony/data/DataNetwork.java +++ b/src/java/com/android/internal/telephony/data/DataNetwork.java @@ -103,6 +103,7 @@ import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthE import com.android.internal.telephony.data.TelephonyNetworkAgent.TelephonyNetworkAgentCallback; import com.android.internal.telephony.flags.FeatureFlags; import com.android.internal.telephony.metrics.DataCallSessionStats; +import com.android.internal.telephony.metrics.DataNetworkValidationStats; import com.android.internal.telephony.metrics.TelephonyMetrics; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FunctionalUtils; @@ -552,6 +553,9 @@ public class DataNetwork extends StateMachine { /** Metrics of per data network connection. */ private final DataCallSessionStats mDataCallSessionStats; + /** Metrics of per data network validation. */ + private final @NonNull DataNetworkValidationStats mDataNetworkValidationStats; + /** * The unique context id assigned by the data service in {@link DataCallResponse#getId()}. One * for {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN} and one for @@ -986,6 +990,7 @@ public class DataNetwork extends StateMachine { mDataNetworkControllerCallback); mDataConfigManager = mDataNetworkController.getDataConfigManager(); mDataCallSessionStats = new DataCallSessionStats(mPhone); + mDataNetworkValidationStats = new DataNetworkValidationStats(mPhone); mDataNetworkCallback = callback; mDataProfile = dataProfile; if (dataProfile.getTrafficDescriptor() != null) { @@ -1919,6 +1924,9 @@ public class DataNetwork extends StateMachine { if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) { unregisterForWwanEvents(); } + // Since NetworkValidation is able to request only in the Connected state, + // if ever connected, log for onDataNetworkDisconnected. + mDataNetworkValidationStats.onDataNetworkDisconnected(getDataNetworkType()); } else { mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback .onSetupDataFailed(DataNetwork.this, @@ -3511,6 +3519,8 @@ public class DataNetwork extends StateMachine { DataService.REQUEST_REASON_HANDOVER, mLinkProperties, mPduSessionId, mNetworkSliceInfo, mHandoverDataProfile.getTrafficDescriptor(), true, obtainMessage(EVENT_HANDOVER_RESPONSE, retryEntry)); + + mDataNetworkValidationStats.onHandoverAttempted(); } /** @@ -3705,6 +3715,11 @@ public class DataNetwork extends StateMachine { // Request validation directly from the data service. mDataServiceManagers.get(mTransport).requestNetworkValidation( mCid.get(mTransport), obtainMessage(EVENT_DATA_NETWORK_VALIDATION_RESPONSE)); + + int apnTypeBitmask = mDataProfile.getApnSetting() != null + ? mDataProfile.getApnSetting().getApnTypeBitmask() : ApnSetting.TYPE_NONE; + mDataNetworkValidationStats.onRequestNetworkValidation(apnTypeBitmask); + log("handleDataNetworkValidationRequest, network validation requested"); } @@ -3753,6 +3768,9 @@ public class DataNetwork extends StateMachine { mNetworkValidationStatus = networkValidationStatus; notifyPreciseDataConnectionState(); } + + mDataNetworkValidationStats.onUpdateNetworkValidationState( + mNetworkValidationStatus, getDataNetworkType()); } /** diff --git a/src/java/com/android/internal/telephony/metrics/DataNetworkValidationStats.java b/src/java/com/android/internal/telephony/metrics/DataNetworkValidationStats.java new file mode 100644 index 0000000000..fdac834562 --- /dev/null +++ b/src/java/com/android/internal/telephony/metrics/DataNetworkValidationStats.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.metrics; + +import android.annotation.ElapsedRealtimeLong; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.SystemClock; +import android.telephony.Annotation.ApnType; +import android.telephony.Annotation.NetworkType; +import android.telephony.PreciseDataConnectionState; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.TelephonyStatsLog; +import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation; + +/** + * DataNetworkValidationStats logs the atoms for response after a validation request from the + * DataNetwork in framework. + */ +public class DataNetworkValidationStats { + + private static final String TAG = DataNetworkValidationStats.class.getSimpleName(); + + @NonNull + private final Phone mPhone; + + @NonNull + private final PersistAtomsStorage mAtomsStorage = + PhoneFactory.getMetricsCollector().getAtomsStorage(); + + @Nullable + private DataNetworkValidation mDataNetworkValidation; + + @ElapsedRealtimeLong + private long mRequestedTimeInMillis; + + /** constructor */ + public DataNetworkValidationStats(@NonNull Phone phone) { + mPhone = phone; + } + + + /** + * Create a new ongoing atom when NetworkValidation requested. + * + * Create a data network validation proto for a new atom record and write the start time to + * calculate the elapsed time required. + * + * @param apnTypeBitMask APN type bitmask of DataNetwork. + */ + public void onRequestNetworkValidation(@ApnType int apnTypeBitMask) { + if (mDataNetworkValidation == null) { + mDataNetworkValidation = getDefaultProto(apnTypeBitMask); + mRequestedTimeInMillis = getTimeMillis(); + } + } + + /** Mark the Handover Attempt field as true if validation was requested */ + public void onHandoverAttempted() { + if (mDataNetworkValidation != null) { + mDataNetworkValidation.handoverAttempted = true; + } + } + + /** + * Called when data network is disconnected. + * + * Since network validation is based on the data network, validation must also end when the data + * network is disconnected. At this time, validation has not been completed, save an atom as + * unspecified. and clear. + * + * @param networkType Current Network Type of the Data Network. + */ + public void onDataNetworkDisconnected(@NetworkType int networkType) { + // Nothing to do, if never requested validation + if (mDataNetworkValidation == null) { + return; + } + + // Set data for and atom. + calcElapsedTime(); + mDataNetworkValidation.networkType = networkType; + mDataNetworkValidation.signalStrength = mPhone.getSignalStrength().getLevel(); + mDataNetworkValidation.validationResult = TelephonyStatsLog + .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_UNSPECIFIED; + + // Store. + mAtomsStorage.addDataNetworkValidation(mDataNetworkValidation); + + // clear all values. + clear(); + } + + /** + * Store an atom by updated state. + * + * Called when the validation status is updated, and saves the atom when a failure or success + * result is received. + * + * @param status Data Network Validation Status. + * @param networkType Current Network Type of the Data Network. + */ + public void onUpdateNetworkValidationState( + @PreciseDataConnectionState.NetworkValidationStatus int status, + @NetworkType int networkType) { + // Nothing to do, if never requested validation + if (mDataNetworkValidation == null) { + return; + } + + switch (status) { + // Immediately after requesting validation, these messages may occur. In this case, + // ignore it and wait for the next update. + case PreciseDataConnectionState.NETWORK_VALIDATION_NOT_REQUESTED: // fall-through + case PreciseDataConnectionState.NETWORK_VALIDATION_IN_PROGRESS: + return; + // If status is unsupported, NetworkValidation should not be requested initially. logs + // this for abnormal tracking. + case PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED: + mDataNetworkValidation.validationResult = TelephonyStatsLog + .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_NOT_SUPPORTED; + break; + // Success or failure corresponds to the result, store an atom. + case PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS: + case PreciseDataConnectionState.NETWORK_VALIDATION_FAILURE: + mDataNetworkValidation.validationResult = status; + break; + } + + // Set data for and atom. + calcElapsedTime(); + mDataNetworkValidation.networkType = networkType; + mDataNetworkValidation.signalStrength = mPhone.getSignalStrength().getLevel(); + + // Store. + mAtomsStorage.addDataNetworkValidation(mDataNetworkValidation); + + // clear all values. + clear(); + } + + /** + * Calculate the current time required based on when network validation is requested. + */ + private void calcElapsedTime() { + if (mDataNetworkValidation != null && mRequestedTimeInMillis != 0) { + mDataNetworkValidation.elapsedTimeInMillis = getTimeMillis() - mRequestedTimeInMillis; + } + } + + /** + * Returns current time in millis from boot. + */ + @VisibleForTesting + @ElapsedRealtimeLong + protected long getTimeMillis() { + return SystemClock.elapsedRealtime(); + } + + /** + * Clear all values. + */ + private void clear() { + mDataNetworkValidation = null; + mRequestedTimeInMillis = 0; + } + + + /** Creates a DataNetworkValidation proto with default values. */ + @NonNull + private DataNetworkValidation getDefaultProto(@ApnType int apnTypeBitmask) { + DataNetworkValidation proto = new DataNetworkValidation(); + proto.networkType = + TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_UNKNOWN; + proto.apnTypeBitmask = apnTypeBitmask; + proto.signalStrength = + TelephonyStatsLog + .DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + proto.validationResult = + TelephonyStatsLog + .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_UNSPECIFIED; + proto.elapsedTimeInMillis = 0; + proto.handoverAttempted = false; + proto.networkValidationCount = 1; + return proto; + } +} + diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java index d7f07c58e9..5eb0894636 100644 --- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java +++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java @@ -20,6 +20,7 @@ import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ID_TABLE_ import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH; import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION; +import static com.android.internal.telephony.TelephonyStatsLog.DATA_NETWORK_VALIDATION; import static com.android.internal.telephony.TelephonyStatsLog.DEVICE_TELEPHONY_PROPERTIES; import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO; import static com.android.internal.telephony.TelephonyStatsLog.GBA_EVENT; @@ -72,6 +73,7 @@ import com.android.internal.telephony.imsphone.ImsPhone; import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState; import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; +import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation; import com.android.internal.telephony.nano.PersistAtomsProto.EmergencyNumbersInfo; import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent; import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent; @@ -226,6 +228,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { registerAtom(SATELLITE_OUTGOING_DATAGRAM); registerAtom(SATELLITE_PROVISION); registerAtom(SATELLITE_SOS_MESSAGE_RECOMMENDER); + registerAtom(DATA_NETWORK_VALIDATION); Rlog.d(TAG, "registered"); } else { Rlog.e(TAG, "could not get StatsManager, atoms not registered"); @@ -320,6 +323,8 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { return pullSatelliteProvision(data); case SATELLITE_SOS_MESSAGE_RECOMMENDER: return pullSatelliteSosMessageRecommender(data); + case DATA_NETWORK_VALIDATION: + return pullDataNetworkValidation(data); default: Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag)); return StatsManager.PULL_SKIP; @@ -940,6 +945,19 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { } } + private int pullDataNetworkValidation(@NonNull List<StatsEvent> data) { + DataNetworkValidation[] dataNetworkValidations = + mStorage.getDataNetworkValidation(mPowerCorrelatedMinCooldownMillis); + if (dataNetworkValidations != null) { + Arrays.stream(dataNetworkValidations) + .forEach(d -> data.add(buildStatsEvent(d))); + return StatsManager.PULL_SUCCESS; + } else { + Rlog.w(TAG, "DATA_NETWORK_VALIDATION pull too frequent, skipping"); + return StatsManager.PULL_SKIP; + } + } + /** Registers a pulled atom ID {@code atomId}. */ private void registerAtom(int atomId) { mStatsManager.setPullAtomCallback(atomId, /* metadata= */ null, @@ -1395,6 +1413,18 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { stats.isSatelliteAllowedInCurrentLocation); } + private static StatsEvent buildStatsEvent(DataNetworkValidation stats) { + return TelephonyStatsLog.buildStatsEvent( + DATA_NETWORK_VALIDATION, + stats.networkType, + stats.apnTypeBitmask, + stats.signalStrength, + stats.validationResult, + stats.elapsedTimeInMillis, + stats.handoverAttempted, + stats.networkValidationCount); + } + /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */ static Phone[] getPhonesIfAny() { try { diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java index f3fe8fa333..8553b48a4b 100644 --- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java +++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java @@ -33,6 +33,7 @@ import com.android.internal.telephony.nano.PersistAtomsProto.CarrierIdMismatch; import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState; import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; +import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation; import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent; import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent; import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent; @@ -173,6 +174,9 @@ public class PersistAtomsStorage { private final int mMaxNumSatelliteStats; private final int mMaxNumSatelliteControllerStats = 1; + /** Maximum number of data network validation to store during pulls. */ + private final int mMaxNumDataNetworkValidation; + /** Stores persist atoms and persist states of the puller. */ @VisibleForTesting protected PersistAtoms mAtoms; @@ -223,6 +227,7 @@ public class PersistAtomsStorage { mMaxNumGbaEventStats = 5; mMaxOutgoingShortCodeSms = 5; mMaxNumSatelliteStats = 5; + mMaxNumDataNetworkValidation = 5; } else { mMaxNumVoiceCallSessions = 50; mMaxNumSms = 25; @@ -247,6 +252,7 @@ public class PersistAtomsStorage { mMaxNumGbaEventStats = 10; mMaxOutgoingShortCodeSms = 10; mMaxNumSatelliteStats = 15; + mMaxNumDataNetworkValidation = 15; } mAtoms = loadAtomsFromFile(); @@ -791,6 +797,25 @@ public class PersistAtomsStorage { saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); } + /** Adds a data network validation to the storage. */ + public synchronized void addDataNetworkValidation(DataNetworkValidation dataNetworkValidation) { + DataNetworkValidation existingStats = find(dataNetworkValidation); + if (existingStats != null) { + int count = existingStats.networkValidationCount + + dataNetworkValidation.networkValidationCount; + long elapsedTime = ((dataNetworkValidation.elapsedTimeInMillis + * dataNetworkValidation.networkValidationCount) + ( + existingStats.elapsedTimeInMillis * existingStats.networkValidationCount)) + / count; + existingStats.networkValidationCount = count; + existingStats.elapsedTimeInMillis = elapsedTime; + } else { + mAtoms.dataNetworkValidation = insertAtRandomPlace( + mAtoms.dataNetworkValidation, dataNetworkValidation, mMaxNumDataCallSessions); + } + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS); + } + /** * Returns and clears the voice call sessions if last pulled longer than {@code * minIntervalMillis} ago, otherwise returns {@code null}. @@ -1449,6 +1474,24 @@ public class PersistAtomsStorage { } } + /** + * Returns and clears the data network validation if last pulled longer than {@code + * minIntervalMillis} ago, otherwise returns {@code null}. + */ + @Nullable + public synchronized DataNetworkValidation[] getDataNetworkValidation(long minIntervalMillis) { + long wallTime = getWallTimeMillis(); + if (wallTime - mAtoms.dataNetworkValidationPullTimestampMillis > minIntervalMillis) { + mAtoms.dataNetworkValidationPullTimestampMillis = wallTime; + DataNetworkValidation[] previousDataNetworkValidation = mAtoms.dataNetworkValidation; + mAtoms.dataNetworkValidation = new DataNetworkValidation[0]; + saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS); + return previousDataNetworkValidation; + } else { + return null; + } + } + /** Saves {@link PersistAtoms} to a file in private storage immediately. */ public synchronized void flushAtoms() { saveAtomsToFile(0); @@ -1599,6 +1642,12 @@ public class PersistAtomsStorage { atoms.satelliteSosMessageRecommender = sanitizeAtoms( atoms.satelliteSosMessageRecommender, SatelliteSosMessageRecommender.class, mMaxNumSatelliteStats); + atoms.dataNetworkValidation = + sanitizeAtoms( + atoms.dataNetworkValidation, + DataNetworkValidation.class, + mMaxNumDataNetworkValidation + ); // out of caution, sanitize also the timestamps atoms.voiceCallRatUsagePullTimestampMillis = @@ -1661,6 +1710,8 @@ public class PersistAtomsStorage { sanitizeTimestamp(atoms.satelliteProvisionPullTimestampMillis); atoms.satelliteSosMessageRecommenderPullTimestampMillis = sanitizeTimestamp(atoms.satelliteSosMessageRecommenderPullTimestampMillis); + atoms.dataNetworkValidationPullTimestampMillis = + sanitizeTimestamp(atoms.dataNetworkValidationPullTimestampMillis); return atoms; } catch (NoSuchFileException e) { Rlog.d(TAG, "PersistAtoms file not found"); @@ -2084,6 +2135,24 @@ public class PersistAtomsStorage { } /** + * Returns SatelliteOutgoingDatagram atom that has same values or {@code null} + * if it does not exist. + */ + private @Nullable DataNetworkValidation find(DataNetworkValidation key) { + for (DataNetworkValidation stats : mAtoms.dataNetworkValidation) { + if (stats.networkType == key.networkType + && stats.apnTypeBitmask == key.apnTypeBitmask + && stats.signalStrength == key.signalStrength + && stats.validationResult == key.validationResult + && stats.handoverAttempted == key.handoverAttempted) { + return stats; + } + } + return null; + } + + + /** * Inserts a new element in a random position in an array with a maximum size. * * <p>If the array is full, merge with existing item if possible or replace one item randomly. @@ -2339,6 +2408,7 @@ public class PersistAtomsStorage { atoms.satelliteOutgoingDatagramPullTimestampMillis = currentTime; atoms.satelliteProvisionPullTimestampMillis = currentTime; atoms.satelliteSosMessageRecommenderPullTimestampMillis = currentTime; + atoms.dataNetworkValidationPullTimestampMillis = currentTime; Rlog.d(TAG, "created new PersistAtoms"); return atoms; diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/DataNetworkValidationStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/DataNetworkValidationStatsTest.java new file mode 100644 index 0000000000..b9493b9725 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/DataNetworkValidationStatsTest.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.metrics; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.telephony.PreciseDataConnectionState; +import android.telephony.SignalStrength; +import android.telephony.TelephonyManager; +import android.telephony.data.ApnSetting; + +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.TelephonyStatsLog; +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.nano.PersistAtomsProto; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** Test DataNetworkValidationStats */ +public class DataNetworkValidationStatsTest extends TelephonyTest { + + /** Initial time at starting tests */ + private static final Long STARTED_TIME_IN_MILLIS = 5000000L; + + private TestableDataNetworkValidationStats mDataNetworkValidationStats; + + /** Test Class for override elapsed time */ + private static class TestableDataNetworkValidationStats extends DataNetworkValidationStats { + private long mSystemClockInMillis; + TestableDataNetworkValidationStats(Phone phone) { + super(phone); + mSystemClockInMillis = STARTED_TIME_IN_MILLIS; + } + + @Override + protected long getTimeMillis() { + return mSystemClockInMillis; + } + + public void elapseTimeInMillis(long elapse) { + mSystemClockInMillis += elapse; + } + } + + @Before + public void setup() throws Exception { + super.setUp(getClass().getSimpleName()); + + mDataNetworkValidationStats = new TestableDataNetworkValidationStats(mPhone); + doReturn(SignalStrength.SIGNAL_STRENGTH_GREAT).when(mSignalStrength).getLevel(); + } + + @After + public void tearDown() throws Exception { + mDataNetworkValidationStats = null; + super.tearDown(); + } + + @Test + public void testRequestDataNetworkValidation() { + mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS); + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + public void testOnUpdateNetworkValidationStateWithSuccessStatus() { + + // Test + mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS); + mDataNetworkValidationStats.elapseTimeInMillis(100L); + mDataNetworkValidationStats.onUpdateNetworkValidationState( + PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS, + TelephonyManager.NETWORK_TYPE_LTE); + + // Verify that atom was logged + ArgumentCaptor<PersistAtomsProto.DataNetworkValidation> captor = + ArgumentCaptor.forClass(PersistAtomsProto.DataNetworkValidation.class); + verify(mPersistAtomsStorage, times(1)).addDataNetworkValidation( + captor.capture()); + PersistAtomsProto.DataNetworkValidation proto = captor.getValue(); + + // Make sure variables + assertEquals( + TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_LTE, + proto.networkType); + assertEquals(ApnSetting.TYPE_IMS, proto.apnTypeBitmask); + assertEquals( + TelephonyStatsLog.DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_GREAT, + proto.signalStrength); + assertEquals(TelephonyStatsLog + .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_SUCCESS, + proto.validationResult); + assertEquals(100L, proto.elapsedTimeInMillis); + assertFalse(proto.handoverAttempted); + assertEquals(1, proto.networkValidationCount); + } + + @Test + public void testOnUpdateNetworkValidationStateWithFailureStatus() { + + // Test + mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_EMERGENCY); + mDataNetworkValidationStats.elapseTimeInMillis(100L); + mDataNetworkValidationStats.onHandoverAttempted(); + mDataNetworkValidationStats.elapseTimeInMillis(100L); + mDataNetworkValidationStats.onUpdateNetworkValidationState( + PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS, + TelephonyManager.NETWORK_TYPE_IWLAN); + + // Verify that atom was logged + ArgumentCaptor<PersistAtomsProto.DataNetworkValidation> captor = + ArgumentCaptor.forClass(PersistAtomsProto.DataNetworkValidation.class); + verify(mPersistAtomsStorage, times(1)).addDataNetworkValidation( + captor.capture()); + PersistAtomsProto.DataNetworkValidation proto = captor.getValue(); + + // Make sure variables + assertEquals( + TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_IWLAN, + proto.networkType); + assertEquals(ApnSetting.TYPE_EMERGENCY, proto.apnTypeBitmask); + assertEquals( + TelephonyStatsLog.DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_GREAT, + proto.signalStrength); + assertEquals(TelephonyStatsLog + .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_SUCCESS, + proto.validationResult); + assertEquals(200L, proto.elapsedTimeInMillis); + assertTrue(proto.handoverAttempted); + assertEquals(1, proto.networkValidationCount); + } + + @Test + public void testOnUpdateNetworkValidationStateWithInProgressStatus() { + + // Test + mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS); + mDataNetworkValidationStats.elapseTimeInMillis(100L); + mDataNetworkValidationStats.onUpdateNetworkValidationState( + PreciseDataConnectionState.NETWORK_VALIDATION_NOT_REQUESTED, + TelephonyManager.NETWORK_TYPE_LTE); + + // Verify + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + public void testOnUpdateNetworkValidationStateWithNotRequestedStatus() { + + // Test + mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS); + mDataNetworkValidationStats.elapseTimeInMillis(100L); + mDataNetworkValidationStats.onUpdateNetworkValidationState( + PreciseDataConnectionState.NETWORK_VALIDATION_NOT_REQUESTED, + TelephonyManager.NETWORK_TYPE_LTE); + + // Verify + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + public void testOnUpdateNetworkValidationStateWithUnsupportedStatus() { + + // Set up + doReturn(SignalStrength.SIGNAL_STRENGTH_POOR).when(mSignalStrength).getLevel(); + + // Test + mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS); + mDataNetworkValidationStats.elapseTimeInMillis(300L); + mDataNetworkValidationStats.onUpdateNetworkValidationState( + PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED, + TelephonyManager.NETWORK_TYPE_LTE); + + // Verify that atom was logged + ArgumentCaptor<PersistAtomsProto.DataNetworkValidation> captor = + ArgumentCaptor.forClass(PersistAtomsProto.DataNetworkValidation.class); + verify(mPersistAtomsStorage, times(1)).addDataNetworkValidation( + captor.capture()); + PersistAtomsProto.DataNetworkValidation proto = captor.getValue(); + + // Make sure variables + assertEquals( + TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_LTE, + proto.networkType); + assertEquals(ApnSetting.TYPE_IMS, proto.apnTypeBitmask); + assertEquals( + TelephonyStatsLog.DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_POOR, + proto.signalStrength); + assertEquals(TelephonyStatsLog + .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_NOT_SUPPORTED, + proto.validationResult); + assertEquals(300L, proto.elapsedTimeInMillis); + assertFalse(proto.handoverAttempted); + assertEquals(1, proto.networkValidationCount); + } + + + @Test + public void testOnDataNetworkDisconnected() { + + // Set up + doReturn(SignalStrength.SIGNAL_STRENGTH_POOR).when(mSignalStrength).getLevel(); + + // Test + mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS); + mDataNetworkValidationStats.elapseTimeInMillis(300L); + mDataNetworkValidationStats.onDataNetworkDisconnected(TelephonyManager.NETWORK_TYPE_LTE); + + // Verify that atom was logged + ArgumentCaptor<PersistAtomsProto.DataNetworkValidation> captor = + ArgumentCaptor.forClass(PersistAtomsProto.DataNetworkValidation.class); + verify(mPersistAtomsStorage, times(1)).addDataNetworkValidation( + captor.capture()); + PersistAtomsProto.DataNetworkValidation proto = captor.getValue(); + + // Make sure variables + assertEquals( + TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_LTE, + proto.networkType); + assertEquals(ApnSetting.TYPE_IMS, proto.apnTypeBitmask); + assertEquals( + TelephonyStatsLog.DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_POOR, + proto.signalStrength); + assertEquals(TelephonyStatsLog + .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_UNSPECIFIED, + proto.validationResult); + assertEquals(300L, proto.elapsedTimeInMillis); + assertFalse(proto.handoverAttempted); + assertEquals(1, proto.networkValidationCount); + } + + @Test + public void testOnUpdateNetworkValidationState_DupStatus() { + + // Test + mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS); + mDataNetworkValidationStats.elapseTimeInMillis(100L); + mDataNetworkValidationStats.onUpdateNetworkValidationState( + PreciseDataConnectionState.NETWORK_VALIDATION_IN_PROGRESS, + TelephonyManager.NETWORK_TYPE_LTE); + mDataNetworkValidationStats.elapseTimeInMillis(100L); + mDataNetworkValidationStats.onUpdateNetworkValidationState( + PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS, + TelephonyManager.NETWORK_TYPE_UMTS); + mDataNetworkValidationStats.elapseTimeInMillis(100L); + mDataNetworkValidationStats.onUpdateNetworkValidationState( + PreciseDataConnectionState.NETWORK_VALIDATION_FAILURE, + TelephonyManager.NETWORK_TYPE_NR); + + // Verify that atom was logged + ArgumentCaptor<PersistAtomsProto.DataNetworkValidation> captor = + ArgumentCaptor.forClass(PersistAtomsProto.DataNetworkValidation.class); + verify(mPersistAtomsStorage, times(1)).addDataNetworkValidation( + captor.capture()); + PersistAtomsProto.DataNetworkValidation proto = captor.getValue(); + + // Make sure variables + assertEquals( + TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_UMTS, + proto.networkType); + assertEquals(ApnSetting.TYPE_IMS, proto.apnTypeBitmask); + assertEquals( + TelephonyStatsLog.DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_GREAT, + proto.signalStrength); + assertEquals(TelephonyStatsLog + .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_SUCCESS, + proto.validationResult); + assertEquals(200L, proto.elapsedTimeInMillis); + assertFalse(proto.handoverAttempted); + assertEquals(1, proto.networkValidationCount); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java index 75ef7ec13b..bc79a564ed 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java @@ -62,10 +62,13 @@ import android.annotation.Nullable; import android.content.Context; import android.os.Build; import android.telephony.DisconnectCause; +import android.telephony.PreciseDataConnectionState; import android.telephony.SatelliteProtoEnums; import android.telephony.ServiceState; +import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.telephony.TelephonyProtoEnums; +import android.telephony.data.ApnSetting; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.SipDelegateManager; @@ -76,6 +79,7 @@ import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState; import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; +import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation; import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent; import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent; import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent; @@ -286,6 +290,12 @@ public class PersistAtomsStorageTest extends TelephonyTest { private SatelliteSosMessageRecommender mSatelliteSosMessageRecommender2; private SatelliteSosMessageRecommender[] mSatelliteSosMessageRecommenders; + private DataNetworkValidation mDataNetworkValidationLte1; + private DataNetworkValidation mDataNetworkValidationLte2; + private DataNetworkValidation mDataNetworkValidationIwlan1; + private DataNetworkValidation mDataNetworkValidationIwlan2; + private DataNetworkValidation[] mDataNetworkValidations; + private void makeTestData() { // MO call with SRVCC (LTE to UMTS) mCall1Proto = new VoiceCallSession(); @@ -1070,6 +1080,10 @@ public class PersistAtomsStorageTest extends TelephonyTest { mOutgoingShortCodeSms = new OutgoingShortCodeSms[] {mOutgoingShortCodeSms1, mOutgoingShortCodeSms2}; + + generateTestSatelliteData(); + + generateTestDataNetworkValidationsData(); } private void generateTestSatelliteData() { @@ -1216,6 +1230,61 @@ public class PersistAtomsStorageTest extends TelephonyTest { }; } + private void generateTestDataNetworkValidationsData() { + + // for LTE #1 + mDataNetworkValidationLte1 = new DataNetworkValidation(); + mDataNetworkValidationLte1.networkType = TelephonyManager.NETWORK_TYPE_LTE; + mDataNetworkValidationLte1.apnTypeBitmask = ApnSetting.TYPE_IMS; + mDataNetworkValidationLte1.signalStrength = SignalStrength.SIGNAL_STRENGTH_GREAT; + mDataNetworkValidationLte1.validationResult = + PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS; + mDataNetworkValidationLte1.elapsedTimeInMillis = 100L; + mDataNetworkValidationLte1.handoverAttempted = false; + mDataNetworkValidationLte1.networkValidationCount = 1; + + // for LTE #2 + mDataNetworkValidationLte2 = new DataNetworkValidation(); + mDataNetworkValidationLte2.networkType = TelephonyManager.NETWORK_TYPE_LTE; + mDataNetworkValidationLte2.apnTypeBitmask = ApnSetting.TYPE_IMS; + mDataNetworkValidationLte2.signalStrength = SignalStrength.SIGNAL_STRENGTH_GREAT; + mDataNetworkValidationLte2.validationResult = + PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS; + mDataNetworkValidationLte2.elapsedTimeInMillis = 100L; + mDataNetworkValidationLte2.handoverAttempted = false; + mDataNetworkValidationLte2.networkValidationCount = 1; + + // for IWLAN #1 + mDataNetworkValidationIwlan1 = new DataNetworkValidation(); + mDataNetworkValidationIwlan1.networkType = TelephonyManager.NETWORK_TYPE_IWLAN; + mDataNetworkValidationIwlan1.apnTypeBitmask = ApnSetting.TYPE_IMS; + mDataNetworkValidationIwlan1.signalStrength = SignalStrength.SIGNAL_STRENGTH_POOR; + mDataNetworkValidationIwlan1.validationResult = + PreciseDataConnectionState.NETWORK_VALIDATION_FAILURE; + mDataNetworkValidationIwlan1.elapsedTimeInMillis = 10000L; + mDataNetworkValidationIwlan1.handoverAttempted = false; + mDataNetworkValidationIwlan1.networkValidationCount = 1; + + // for IWLAN #2 + mDataNetworkValidationIwlan2 = new DataNetworkValidation(); + mDataNetworkValidationIwlan2.networkType = TelephonyManager.NETWORK_TYPE_IWLAN; + mDataNetworkValidationIwlan2.apnTypeBitmask = ApnSetting.TYPE_IMS; + mDataNetworkValidationIwlan2.signalStrength = SignalStrength.SIGNAL_STRENGTH_POOR; + mDataNetworkValidationIwlan2.validationResult = + PreciseDataConnectionState.NETWORK_VALIDATION_FAILURE; + mDataNetworkValidationIwlan2.elapsedTimeInMillis = 30000L; + mDataNetworkValidationIwlan2.handoverAttempted = false; + mDataNetworkValidationIwlan2.networkValidationCount = 1; + + mDataNetworkValidations = + new DataNetworkValidation[] { + mDataNetworkValidationLte1, + mDataNetworkValidationLte1, + mDataNetworkValidationIwlan1, + mDataNetworkValidationIwlan2, + }; + } + private static class TestablePersistAtomsStorage extends PersistAtomsStorage { private long mTimeMillis = START_TIME_MILLIS; @@ -1381,6 +1450,10 @@ public class PersistAtomsStorageTest extends TelephonyTest { mSatelliteSosMessageRecommender1 = null; mSatelliteSosMessageRecommender2 = null; mSatelliteSosMessageRecommenders = null; + mDataNetworkValidationLte1 = null; + mDataNetworkValidationLte2 = null; + mDataNetworkValidationIwlan1 = null; + mDataNetworkValidationIwlan2 = null; super.tearDown(); } @@ -4513,6 +4586,77 @@ public class PersistAtomsStorageTest extends TelephonyTest { @Test @SmallTest + public void addDataNetworkValidation_newEntry() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + + mPersistAtomsStorage.addDataNetworkValidation(mDataNetworkValidationLte1); + mPersistAtomsStorage.addDataNetworkValidation(mDataNetworkValidationIwlan1); + mPersistAtomsStorage.incTimeMillis(100L); + + // There should be 2 DataNetworkValidation + verifyCurrentStateSavedToFileOnce(); + DataNetworkValidation[] output = mPersistAtomsStorage.getDataNetworkValidation(0L); + assertProtoArrayEqualsIgnoringOrder( + new DataNetworkValidation[] { + mDataNetworkValidationLte1, mDataNetworkValidationIwlan1}, + output); + } + + @Test + public void addDataNetworkValidation_existingEntry() throws Exception { + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + + int expectedNetworkValidationCount = + mDataNetworkValidationLte1.networkValidationCount + + mDataNetworkValidationLte2.networkValidationCount; + + mPersistAtomsStorage.addDataNetworkValidation(mDataNetworkValidationLte1); + mPersistAtomsStorage.addDataNetworkValidation(mDataNetworkValidationLte2); + mPersistAtomsStorage.incTimeMillis(100L); + + DataNetworkValidation expected = copyOf(mDataNetworkValidationLte1); + expected.networkValidationCount = expectedNetworkValidationCount; + + // There should be 1 DataNetworkValidation + verifyCurrentStateSavedToFileOnce(); + DataNetworkValidation[] output = + mPersistAtomsStorage.getDataNetworkValidation(0L); + assertProtoArrayEqualsIgnoringOrder( + new DataNetworkValidation[] {expected}, + output); + } + + @Test + public void addDataNetworkValidation_tooManyEntries() throws Exception { + + // Set up + doReturn(false).when(mPackageManager).hasSystemFeature(anyString()); + + createEmptyTestFile(); + mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); + + // Currently, the maximum number that can be stored for DataNetworkValidation in Atom + // storage is 15. Try saving excess. + int maxNumDataNetworkValidation = 20; + mPersistAtomsStorage.addDataNetworkValidation(mDataNetworkValidationLte1); + for (int i = 0; i < maxNumDataNetworkValidation; i++) { + DataNetworkValidation copied = copyOf(mDataNetworkValidationLte1); + copied.apnTypeBitmask = mDataNetworkValidationLte1.apnTypeBitmask + i; + mPersistAtomsStorage.addDataNetworkValidation(copied); + } + mPersistAtomsStorage.incTimeMillis(100L); + + // There should be less than or equal to maxNumDataNetworkValidation + verifyCurrentStateSavedToFileOnce(); + DataNetworkValidation[] output = + mPersistAtomsStorage.getDataNetworkValidation(0L); + assertTrue(output.length <= maxNumDataNetworkValidation); + } + + @Test + @SmallTest public void clearAtoms() throws Exception { createTestFile(START_TIME_MILLIS); mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext); @@ -4596,6 +4740,8 @@ public class PersistAtomsStorageTest extends TelephonyTest { atoms.satelliteProvisionPullTimestampMillis = lastPullTimeMillis; atoms.satelliteSosMessageRecommender = mSatelliteSosMessageRecommenders; atoms.satelliteSosMessageRecommenderPullTimestampMillis = lastPullTimeMillis; + atoms.dataNetworkValidation = mDataNetworkValidations; + atoms.dataNetworkValidationPullTimestampMillis = lastPullTimeMillis; FileOutputStream stream = new FileOutputStream(mTestFile); stream.write(PersistAtoms.toByteArray(atoms)); stream.close(); @@ -4759,6 +4905,11 @@ public class PersistAtomsStorageTest extends TelephonyTest { return SatelliteSosMessageRecommender.parseFrom(MessageNano.toByteArray(source)); } + private static DataNetworkValidation copyOf(DataNetworkValidation source) + throws Exception { + return DataNetworkValidation.parseFrom(MessageNano.toByteArray(source)); + } + private void assertAllPullTimestampEquals(long timestamp) { assertEquals( timestamp, |