diff options
4 files changed, 379 insertions, 1 deletions
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto index 288852f03e..0a1d5530f9 100644 --- a/proto/src/persist_atoms.proto +++ b/proto/src/persist_atoms.proto @@ -365,6 +365,7 @@ message DataCallSession { repeated int32 handover_failure_causes = 20; repeated int32 handover_failure_rat = 21; optional bool is_non_dds = 22; + optional bool is_iwlan_cross_sim = 23; } message CellularServiceState { diff --git a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java index afb87dd8ae..2b3ed825a1 100644 --- a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java +++ b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java @@ -18,7 +18,13 @@ package com.android.internal.telephony.metrics; import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.os.Handler; +import android.os.HandlerThread; import android.os.SystemClock; import android.telephony.Annotation.ApnType; import android.telephony.Annotation.DataFailureCause; @@ -55,6 +61,8 @@ public class DataCallSessionStats { private long mStartTime; @Nullable private DataCallSession mDataCallSession; + private Network mSystemDefaultNetwork; + private boolean mIsSystemDefaultNetworkMobile; private final PersistAtomsStorage mAtomsStorage = PhoneFactory.getMetricsCollector().getAtomsStorage(); @@ -62,8 +70,47 @@ public class DataCallSessionStats { public static final int SIZE_LIMIT_HANDOVER_FAILURES = 15; + final class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback { + @Override + public void onAvailable(@NonNull Network network) { + mSystemDefaultNetwork = network; + } + + @Override + public void onCapabilitiesChanged(@NonNull Network network, + @NonNull NetworkCapabilities nc) { + if (network == mSystemDefaultNetwork) { + mIsSystemDefaultNetworkMobile = nc.hasTransport( + NetworkCapabilities.TRANSPORT_CELLULAR); + } + } + + @Override + public void onLost(@NonNull Network network) { + mIsSystemDefaultNetworkMobile = false; + mSystemDefaultNetwork = null; + } + } + public DataCallSessionStats(Phone phone) { mPhone = phone; + registerSystemDefaultNetworkCallback(phone); + } + + private void registerSystemDefaultNetworkCallback(@NonNull Phone phone) { + ConnectivityManager connectivityManager = phone.getContext() + .getSystemService(ConnectivityManager.class); + if (connectivityManager != null) { + HandlerThread handlerThread = new HandlerThread( + DataCallSessionStats.class.getSimpleName()); + handlerThread.start(); + Handler callbackHandler = new Handler(handlerThread.getLooper()); + DefaultNetworkCallback mDefaultNetworkCallback = new DefaultNetworkCallback(); + connectivityManager.registerSystemDefaultNetworkCallback( + mDefaultNetworkCallback, callbackHandler); + } else { + loge("registerSystemDefaultNetworkCallback: ConnectivityManager is null!"); + } } /** Creates a new ongoing atom when data call is set up. */ @@ -101,6 +148,9 @@ public class DataCallSessionStats { (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN) ? 0 : ServiceStateStats.getBand(mPhone); + // Limitation: Will not capture IKE mobility between Backup Calling <-> WiFi Calling. + mDataCallSession.isIwlanCrossSim = currentRat == TelephonyManager.NETWORK_TYPE_IWLAN + && mIsSystemDefaultNetworkMobile; } // only set if apn hasn't been set during setup @@ -199,6 +249,8 @@ public class DataCallSessionStats { if (mDataCallSession.ratAtEnd != currentRat) { mDataCallSession.ratSwitchCount++; mDataCallSession.ratAtEnd = currentRat; + mDataCallSession.isIwlanCrossSim = currentRat == TelephonyManager.NETWORK_TYPE_IWLAN + && mIsSystemDefaultNetworkMobile; } // band may have changed even if RAT was the same mDataCallSession.bandAtEnd = @@ -288,6 +340,7 @@ public class DataCallSessionStats { copy.handoverFailureRat = Arrays.copyOf(call.handoverFailureRat, call.handoverFailureRat.length); copy.isNonDds = call.isNonDds; + copy.isIwlanCrossSim = call.isIwlanCrossSim; return copy; } @@ -313,6 +366,7 @@ public class DataCallSessionStats { proto.handoverFailureCauses = new int[0]; proto.handoverFailureRat = new int[0]; proto.isNonDds = false; + proto.isIwlanCrossSim = false; 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 3171f86b21..2ac10e7eee 100644 --- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java +++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java @@ -1061,7 +1061,8 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { dataCallSession.bandAtEnd, dataCallSession.handoverFailureCauses, dataCallSession.handoverFailureRat, - dataCallSession.isNonDds); + dataCallSession.isNonDds, + dataCallSession.isIwlanCrossSim); } private static StatsEvent buildStatsEvent(ImsRegistrationStats stats) { diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java new file mode 100644 index 0000000000..700b4cfefa --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2023 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.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.telephony.DataFailCause; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.telephony.data.ApnSetting; +import android.telephony.data.DataCallResponse; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +public class DataCallSessionStatsTest extends TelephonyTest { + + private ArgumentCaptor<NetworkCallback> mNetworkCallbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + private DataCallResponse mDefaultImsResponse = buildDataCallResponse("ims", 0); + private Network mMockNetwork; + private NetworkCapabilities mCellularNetworkCapabilities = new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build(); + + private static class TestableDataCallSessionStats extends DataCallSessionStats { + private long mTimeMillis = 0L; + + TestableDataCallSessionStats(Phone phone) { + super(phone); + } + + @Override + protected long getTimeMillis() { + return mTimeMillis; + } + + private void setTimeMillis(long timeMillis) { + mTimeMillis = timeMillis; + } + } + + private TestableDataCallSessionStats mDataCallSessionStats; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + doNothing().when(mConnectivityManager).registerSystemDefaultNetworkCallback( + mNetworkCallbackCaptor.capture(), any()); + mMockNetwork = mock(Network.class); + when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE); + mDataCallSessionStats = new TestableDataCallSessionStats(mPhone); + } + + @After + public void tearDown() throws Exception { + mDataCallSessionStats = null; + super.tearDown(); + } + + private NetworkCallback getNetworkMonitorCallback() { + return mNetworkCallbackCaptor.getValue(); + } + + private DataCallResponse buildDataCallResponse(String apn, long retryDurationMillis) { + return new DataCallResponse.Builder() + .setId(apn.hashCode()) + .setRetryDurationMillis(retryDurationMillis) + .build(); + } + + @Test + @SmallTest + public void testSetupDataCallOnCellularIms_success() { + mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS); + mDataCallSessionStats.onSetupDataCallResponse( + mDefaultImsResponse, + TelephonyManager.NETWORK_TYPE_LTE, + ApnSetting.TYPE_IMS, + ApnSetting.PROTOCOL_IP, + DataFailCause.NONE); + + mDataCallSessionStats.setTimeMillis(60000L); + mDataCallSessionStats.conclude(); + + ArgumentCaptor<DataCallSession> callCaptor = + ArgumentCaptor.forClass(DataCallSession.class); + verify(mPersistAtomsStorage, times(1)).addDataCallSession( + callCaptor.capture()); + DataCallSession stats = callCaptor.getValue(); + + assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask); + assertEquals(1, stats.durationMinutes); + assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.ratAtEnd); + assertTrue(stats.ongoing); + } + + @Test + @SmallTest + public void testSetupDataCallOnIwlan_success() { + mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS); + mDataCallSessionStats.onSetupDataCallResponse( + mDefaultImsResponse, + TelephonyManager.NETWORK_TYPE_IWLAN, + ApnSetting.TYPE_IMS, + ApnSetting.PROTOCOL_IP, + DataFailCause.NONE); + + mDataCallSessionStats.setTimeMillis(120000L); + mDataCallSessionStats.conclude(); + + ArgumentCaptor<DataCallSession> callCaptor = + ArgumentCaptor.forClass(DataCallSession.class); + verify(mPersistAtomsStorage, times(1)).addDataCallSession( + callCaptor.capture()); + DataCallSession stats = callCaptor.getValue(); + + assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask); + assertEquals(2, stats.durationMinutes); + assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.ratAtEnd); + assertFalse(stats.isIwlanCrossSim); + assertTrue(stats.ongoing); + } + + @Test + @SmallTest + public void testSetupDataCallOnCrossSimCalling_success() { + getNetworkMonitorCallback().onAvailable(mMockNetwork); + getNetworkMonitorCallback().onCapabilitiesChanged( + mMockNetwork, mCellularNetworkCapabilities); + mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS); + mDataCallSessionStats.onSetupDataCallResponse( + mDefaultImsResponse, + TelephonyManager.NETWORK_TYPE_IWLAN, + ApnSetting.TYPE_IMS, + ApnSetting.PROTOCOL_IP, + DataFailCause.NONE); + + mDataCallSessionStats.setTimeMillis(60000L); + mDataCallSessionStats.conclude(); + + ArgumentCaptor<DataCallSession> callCaptor = + ArgumentCaptor.forClass(DataCallSession.class); + verify(mPersistAtomsStorage, times(1)).addDataCallSession( + callCaptor.capture()); + DataCallSession stats = callCaptor.getValue(); + + assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask); + assertEquals(1, stats.durationMinutes); + assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.ratAtEnd); + assertTrue(stats.isIwlanCrossSim); + assertTrue(stats.ongoing); + } + + @Test + @SmallTest + public void testSetupDataCallOnCellularIms_failure() { + mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS); + mDataCallSessionStats.onSetupDataCallResponse( + mDefaultImsResponse, + TelephonyManager.NETWORK_TYPE_LTE, + ApnSetting.TYPE_IMS, + ApnSetting.PROTOCOL_IP, + DataFailCause.NETWORK_FAILURE); + + ArgumentCaptor<DataCallSession> callCaptor = + ArgumentCaptor.forClass(DataCallSession.class); + verify(mPersistAtomsStorage, times(1)).addDataCallSession( + callCaptor.capture()); + DataCallSession stats = callCaptor.getValue(); + + assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask); + assertEquals(0, stats.durationMinutes); + assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.ratAtEnd); + assertFalse(stats.ongoing); + } + + @Test + @SmallTest + public void testHandoverFromCellularToIwlan_success() { + mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS); + mDataCallSessionStats.onSetupDataCallResponse( + mDefaultImsResponse, + TelephonyManager.NETWORK_TYPE_LTE, + ApnSetting.TYPE_IMS, + ApnSetting.PROTOCOL_IP, + DataFailCause.NONE); + + mDataCallSessionStats.onDrsOrRatChanged(TelephonyManager.NETWORK_TYPE_IWLAN); + mDataCallSessionStats.conclude(); + + ArgumentCaptor<DataCallSession> callCaptor = + ArgumentCaptor.forClass(DataCallSession.class); + verify(mPersistAtomsStorage, times(1)).addDataCallSession( + callCaptor.capture()); + DataCallSession stats = callCaptor.getValue(); + + assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask); + assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.ratAtEnd); + assertEquals(1, stats.ratSwitchCount); + assertTrue(stats.ongoing); + } + + @Test + @SmallTest + public void testHandoverFromCellularToCrossSimCalling_success() { + mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS); + mDataCallSessionStats.onSetupDataCallResponse( + mDefaultImsResponse, + TelephonyManager.NETWORK_TYPE_LTE, + ApnSetting.TYPE_IMS, + ApnSetting.PROTOCOL_IP, + DataFailCause.NONE); + + getNetworkMonitorCallback().onAvailable(mMockNetwork); + getNetworkMonitorCallback().onCapabilitiesChanged( + mMockNetwork, mCellularNetworkCapabilities); + mDataCallSessionStats.onDrsOrRatChanged(TelephonyManager.NETWORK_TYPE_IWLAN); + mDataCallSessionStats.conclude(); + + ArgumentCaptor<DataCallSession> callCaptor = + ArgumentCaptor.forClass(DataCallSession.class); + verify(mPersistAtomsStorage, times(1)).addDataCallSession( + callCaptor.capture()); + DataCallSession stats = callCaptor.getValue(); + + assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask); + assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.ratAtEnd); + assertEquals(1, stats.ratSwitchCount); + assertTrue(stats.isIwlanCrossSim); + assertTrue(stats.ongoing); + } + + @Test + @SmallTest + public void testHandoverFromCellularToIwlan_failure() { + mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS); + mDataCallSessionStats.onSetupDataCallResponse( + mDefaultImsResponse, + TelephonyManager.NETWORK_TYPE_LTE, + ApnSetting.TYPE_IMS, + ApnSetting.PROTOCOL_IP, + DataFailCause.NONE); + + mDataCallSessionStats.onHandoverFailure(DataFailCause.IWLAN_DNS_RESOLUTION_TIMEOUT, + TelephonyManager.NETWORK_TYPE_LTE, TelephonyManager.NETWORK_TYPE_IWLAN); + mDataCallSessionStats.conclude(); + + ArgumentCaptor<DataCallSession> callCaptor = + ArgumentCaptor.forClass(DataCallSession.class); + verify(mPersistAtomsStorage, times(1)).addDataCallSession( + callCaptor.capture()); + DataCallSession stats = callCaptor.getValue(); + + assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask); + assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.ratAtEnd); + assertTrue(stats.ongoing); + assertEquals(DataFailCause.IWLAN_DNS_RESOLUTION_TIMEOUT, + stats.handoverFailureCauses[0]); + + int cellularToIwlanFailureDirection = TelephonyManager.NETWORK_TYPE_LTE + | (TelephonyManager.NETWORK_TYPE_IWLAN << 16); + assertEquals(cellularToIwlanFailureDirection, stats.handoverFailureRat[0]); + } + + @Test + @SmallTest + public void testSetupDataCallOnIwlan_success_thenOOS() { + mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS); + mDataCallSessionStats.onSetupDataCallResponse( + mDefaultImsResponse, + TelephonyManager.NETWORK_TYPE_IWLAN, + ApnSetting.TYPE_IMS, + ApnSetting.PROTOCOL_IP, + DataFailCause.NONE); + when(mServiceState.getDataRegistrationState()) + .thenReturn(ServiceState.STATE_OUT_OF_SERVICE); + mDataCallSessionStats.onDataCallDisconnected(DataFailCause.IWLAN_IKE_DPD_TIMEOUT); + + ArgumentCaptor<DataCallSession> callCaptor = + ArgumentCaptor.forClass(DataCallSession.class); + verify(mPersistAtomsStorage, times(1)).addDataCallSession( + callCaptor.capture()); + DataCallSession stats = callCaptor.getValue(); + + assertEquals(ApnSetting.TYPE_IMS, stats.apnTypeBitmask); + assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, stats.ratAtEnd); + assertTrue(stats.oosAtEnd); + assertFalse(stats.ongoing); + } +} |