Loading src/java/com/android/internal/telephony/metrics/MetricsCollector.java +23 −57 Original line number Diff line number Diff line Loading @@ -16,10 +16,6 @@ package com.android.internal.telephony.metrics; import static android.telephony.PhoneNumberUtils.areSamePhoneNumber; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_IMS; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_UICC; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.SECOND_IN_MILLIS; Loading Loading @@ -55,14 +51,11 @@ import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSIO import android.annotation.Nullable; import android.app.StatsManager; import android.content.Context; import android.telephony.SubscriptionInfo; import android.text.TextUtils; import android.util.StatsEvent; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyStatsLog; import com.android.internal.telephony.imsphone.ImsPhone; import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; Loading Loading @@ -94,7 +87,6 @@ import com.android.telephony.Rlog; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; Loading Loading @@ -137,23 +129,20 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { .build(); private final PersistAtomsStorage mStorage; private final TelephonyStatsLogHelper mTelephonyStatsLog; private final StatsManager mStatsManager; private final AirplaneModeStats mAirplaneModeStats; private final Set<DataCallSessionStats> mOngoingDataCallStats = ConcurrentHashMap.newKeySet(); private static final Random sRandom = new Random(); public MetricsCollector(Context context) { this(context, new PersistAtomsStorage(context), new TelephonyStatsLogHelper()); this(context, new PersistAtomsStorage(context)); } /** Allows dependency injection. Used during unit tests. */ @VisibleForTesting public MetricsCollector(Context context, PersistAtomsStorage storage, TelephonyStatsLogHelper telephonyStatsLog) { PersistAtomsStorage storage) { mStorage = storage; mTelephonyStatsLog = telephonyStatsLog; mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER); if (mStatsManager != null) { registerAtom(CELLULAR_DATA_SERVICE_SWITCH, POLICY_PULL_DAILY); Loading Loading @@ -656,56 +645,33 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { } private int pullPerSimStatus(List<StatsEvent> data) { SubscriptionController subscriptionController = SubscriptionController.getInstance(); if (subscriptionController == null) { return StatsManager.PULL_SKIP; } int result = StatsManager.PULL_SKIP; for (Phone phone : getPhonesIfAny()) { int subId = phone.getSubId(); String countryIso = Optional.ofNullable(subscriptionController.getSubscriptionInfo(subId)) .map(SubscriptionInfo::getCountryIso) .orElse(""); // number[]- hone numbers from each sources: // numberState[] - int representation for each number source used in the atom // index 0 - PHONE_NUMBER_SOURCE_UICC // index 1 - PHONE_NUMBER_SOURCE_CARRIER // index 2 - PHONE_NUMBER_SOURCE_IMS String[] number = new String[] { subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_UICC), // 0 subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_CARRIER), // 1 subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_IMS), // 2 }; int[] numberState = new int[number.length]; // default value 0 for (int i = 0, stateForNextUniqueNumber = 1; i < numberState.length; i++) { if (TextUtils.isEmpty(number[i])) { // keep state 0 if number not available PerSimStatus perSimStatus = PerSimStatus.getCurrentState(phone); if (perSimStatus == null) { continue; } // the number is available: // try to find the same number from other sources and reuse the state for (int j = 0; j < i; j++) { if (!TextUtils.isEmpty(number[j]) && areSamePhoneNumber(number[i], number[j], countryIso)) { numberState[i] = numberState[j]; } } // didn't find same number (otherwise should not be state 0), assign a new state if (numberState[i] == 0) { numberState[i] = stateForNextUniqueNumber; stateForNextUniqueNumber++; } } StatsEvent statsEvent = mTelephonyStatsLog.buildStatsEvent( StatsEvent statsEvent = TelephonyStatsLog.buildStatsEvent( PER_SIM_STATUS, phone.getPhoneId(), // simSlotIndex phone.getCarrierId(), // carrierId numberState[0], // phoneNumberSourceUicc numberState[1], // phoneNumberSourceCarrier numberState[2]); // phoneNumberSourceIms perSimStatus.carrierId, // carrierId perSimStatus.phoneNumberSourceUicc, // phoneNumberSourceUicc perSimStatus.phoneNumberSourceCarrier, // phoneNumberSourceCarrier perSimStatus.phoneNumberSourceIms, // phoneNumberSourceIms perSimStatus.advancedCallingSettingEnabled, // volteEnabled perSimStatus.voWiFiSettingEnabled, // wfcEnabled perSimStatus.voWiFiModeSetting, // wfcMode perSimStatus.voWiFiRoamingModeSetting, // wfcRoamingMode perSimStatus.vtSettingEnabled, // videoCallingEnabled perSimStatus.dataRoamingEnabled, // dataRoamingEnabled perSimStatus.preferredNetworkType, // allowedNetworksByUser perSimStatus.disabled2g, // is2gDisabled false, // TODO(b/215758472): isPin1Enabled 0); // TODO(b/215758472): simVoltageClass data.add(statsEvent); result = StatsManager.PULL_SUCCESS; } return StatsManager.PULL_SUCCESS; return result; } /** Registers a pulled atom ID {@code atomId} with optional {@code policy} for pulling. */ Loading src/java/com/android/internal/telephony/metrics/PerSimStatus.java 0 → 100644 +199 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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 android.telephony.PhoneNumberUtils.areSamePhoneNumber; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_IMS; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_UICC; import static com.android.internal.telephony.TelephonyStatsLog.PER_SIM_STATUS__WFC_MODE__CELLULAR_PREFERRED; import static com.android.internal.telephony.TelephonyStatsLog.PER_SIM_STATUS__WFC_MODE__UNKNOWN; import static com.android.internal.telephony.TelephonyStatsLog.PER_SIM_STATUS__WFC_MODE__WIFI_ONLY; import static com.android.internal.telephony.TelephonyStatsLog.PER_SIM_STATUS__WFC_MODE__WIFI_PREFERRED; import android.annotation.Nullable; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; import android.telephony.ims.ImsManager; import android.telephony.ims.ImsMmTelManager; import android.text.TextUtils; import com.android.internal.telephony.Phone; import com.android.internal.telephony.SubscriptionController; import java.util.Optional; /** Stores the per SIM status. */ public class PerSimStatus { private static final long BITMASK_2G = TelephonyManager.NETWORK_TYPE_BITMASK_GSM | TelephonyManager.NETWORK_TYPE_BITMASK_GPRS | TelephonyManager.NETWORK_TYPE_BITMASK_EDGE | TelephonyManager.NETWORK_TYPE_BITMASK_CDMA | TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT; public final int carrierId; public final int phoneNumberSourceUicc; public final int phoneNumberSourceCarrier; public final int phoneNumberSourceIms; public final boolean advancedCallingSettingEnabled; public final boolean voWiFiSettingEnabled; public final int voWiFiModeSetting; public final int voWiFiRoamingModeSetting; public final boolean vtSettingEnabled; public final boolean dataRoamingEnabled; public final long preferredNetworkType; public final boolean disabled2g; /** Returns the current sim status of the given {@link Phone}. */ @Nullable public static PerSimStatus getCurrentState(Phone phone) { int[] numberState = getNumberState(phone); if (numberState == null) return null; ImsMmTelManager imsMmTelManager = getImsMmTelManager(phone); return new PerSimStatus( phone.getCarrierId(), numberState[0], numberState[1], numberState[2], imsMmTelManager == null ? false : imsMmTelManager.isAdvancedCallingSettingEnabled(), imsMmTelManager == null ? false : imsMmTelManager.isVoWiFiSettingEnabled(), imsMmTelManager == null ? PER_SIM_STATUS__WFC_MODE__UNKNOWN : wifiCallingModeToProtoEnum(imsMmTelManager.getVoWiFiModeSetting()), imsMmTelManager == null ? PER_SIM_STATUS__WFC_MODE__UNKNOWN : wifiCallingModeToProtoEnum(imsMmTelManager.getVoWiFiRoamingModeSetting()), imsMmTelManager == null ? false : imsMmTelManager.isVtSettingEnabled(), phone.getDataRoamingEnabled(), phone.getAllowedNetworkTypes(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER), is2gDisabled(phone)); } private PerSimStatus( int carrierId, int phoneNumberSourceUicc, int phoneNumberSourceCarrier, int phoneNumberSourceIms, boolean advancedCallingSettingEnabled, boolean voWiFiSettingEnabled, int voWiFiModeSetting, int voWiFiRoamingModeSetting, boolean vtSettingEnabled, boolean dataRoamingEnabled, long preferredNetworkType, boolean disabled2g) { this.carrierId = carrierId; this.phoneNumberSourceUicc = phoneNumberSourceUicc; this.phoneNumberSourceCarrier = phoneNumberSourceCarrier; this.phoneNumberSourceIms = phoneNumberSourceIms; this.advancedCallingSettingEnabled = advancedCallingSettingEnabled; this.voWiFiSettingEnabled = voWiFiSettingEnabled; this.voWiFiModeSetting = voWiFiModeSetting; this.voWiFiRoamingModeSetting = voWiFiRoamingModeSetting; this.vtSettingEnabled = vtSettingEnabled; this.dataRoamingEnabled = dataRoamingEnabled; this.preferredNetworkType = preferredNetworkType; this.disabled2g = disabled2g; } @Nullable private static ImsMmTelManager getImsMmTelManager(Phone phone) { ImsManager imsManager = phone.getContext().getSystemService(ImsManager.class); if (imsManager == null) { return null; } try { return imsManager.getImsMmTelManager(phone.getSubId()); } catch (IllegalArgumentException e) { return null; // Invalid subId } } /** * Returns an array containing the various phone numbers available on the device. * <ul> * <li>Index 0: phone number associated with {@code PHONE_NUMBER_SOURCE_UICC}.</li> * <li>Index 1: phone number associated with {@code PHONE_NUMBER_SOURCE_CARRIER}.</li> * <li>Index 2: phone number associated with {@code PHONE_NUMBER_SOURCE_IMS}.</li> * </ul> */ @Nullable private static int[] getNumberState(Phone phone) { SubscriptionController subscriptionController = SubscriptionController.getInstance(); if (subscriptionController == null) { return null; } int subId = phone.getSubId(); String countryIso = Optional.ofNullable(subscriptionController.getSubscriptionInfo(subId)) .map(SubscriptionInfo::getCountryIso) .orElse(""); // number[] - hone numbers from each sources: String[] number = new String[] { subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_UICC), // 0 subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_CARRIER), // 1 subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_IMS), // 2 }; int[] numberState = new int[number.length]; // default value 0 for (int i = 0, stateForNextUniqueNumber = 1; i < numberState.length; i++) { if (TextUtils.isEmpty(number[i])) { // keep state 0 if number not available continue; } // the number is available: // try to find the same number from other sources and reuse the state for (int j = 0; j < i; j++) { if (!TextUtils.isEmpty(number[j]) && areSamePhoneNumber(number[i], number[j], countryIso)) { numberState[i] = numberState[j]; } } // didn't find same number (otherwise should not be state 0), assign a new state if (numberState[i] == 0) { numberState[i] = stateForNextUniqueNumber++; } } return numberState; } /** * Returns {@code true} if 2G cellular network is disabled (Allow 2G toggle in the settings). */ private static boolean is2gDisabled(Phone phone) { return (phone.getAllowedNetworkTypes( TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G) & BITMASK_2G) == 0; } /** Converts {@link ImsMmTelManager.WifiCallingMode} to the value of PerSimStatus WfcMode. */ private static int wifiCallingModeToProtoEnum(@ImsMmTelManager.WiFiCallingMode int mode) { switch (mode) { case ImsMmTelManager.WIFI_MODE_WIFI_ONLY: return PER_SIM_STATUS__WFC_MODE__WIFI_ONLY; case ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED: return PER_SIM_STATUS__WFC_MODE__CELLULAR_PREFERRED; case ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED: return PER_SIM_STATUS__WFC_MODE__WIFI_PREFERRED; default: return PER_SIM_STATUS__WFC_MODE__UNKNOWN; } } } src/java/com/android/internal/telephony/metrics/TelephonyStatsLogHelper.javadeleted 100644 → 0 +0 −47 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.util.StatsEvent; import com.android.internal.telephony.TelephonyStatsLog; /** * Wrapper of generated TelephonyStatsLog class. Can be mocked during unit test. * * <p>Methods here have the same name/arguments/return as their counterparts in TelephonyStatsLog * and simply call their counterparts. Do not put any involved logic here as they cannot be * covered by unit test. */ public class TelephonyStatsLogHelper { /** Wraps TelephonyStatsLog.buildStatsEvent(...) for atom PER_SIM_STATUS. */ public StatsEvent buildStatsEvent( int atomId, int simSlotIndex, int carrierId, int phoneNumberSourceUicc, int phoneNumberSourceCarrier, int phoneNumberSourceIms) { return TelephonyStatsLog.buildStatsEvent( atomId, simSlotIndex, carrierId, phoneNumberSourceUicc, phoneNumberSourceCarrier, phoneNumberSourceIms); } } tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java +3 −0 Original line number Diff line number Diff line Loading @@ -287,6 +287,8 @@ public class ContextFixture implements TestFixture<Context> { return mPowerWhitelistManager; case Context.LOCATION_SERVICE: return mLocationManager; case Context.TELEPHONY_IMS_SERVICE: return mImsManager; default: return null; } Loading Loading @@ -701,6 +703,7 @@ public class ContextFixture implements TestFixture<Context> { private final ContentProvider mContentProvider = spy(new FakeContentProvider()); private final ImsManager mImsManager = mock(ImsManager.class); private final Configuration mConfiguration = new Configuration(); private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); private final SharedPreferences mSharedPreferences = PreferenceManager Loading tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java +1 −72 Original line number Diff line number Diff line Loading @@ -16,13 +16,8 @@ package com.android.internal.telephony.metrics; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_IMS; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_UICC; 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.PER_SIM_STATUS; import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE; import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY; import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE; Loading @@ -33,20 +28,17 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.app.StatsManager; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.util.StatsEvent; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState; Loading Loading @@ -91,7 +83,6 @@ public class MetricsCollectorTest extends TelephonyTest { // b/153195691: we cannot verify the contents of StatsEvent as its getters are marked with @hide @Mock private Phone mSecondPhone; @Mock private TelephonyStatsLogHelper mTelephonyStatsLog; @Mock private UiccSlot mPhysicalSlot; @Mock private UiccSlot mEsimSlot; @Mock private UiccCard mActiveCard; Loading @@ -105,7 +96,7 @@ public class MetricsCollectorTest extends TelephonyTest { public void setUp() throws Exception { super.setUp(getClass().getSimpleName()); mMetricsCollector = new MetricsCollector(mContext, mPersistAtomsStorage, mTelephonyStatsLog); new MetricsCollector(mContext, mPersistAtomsStorage); doReturn(mSST).when(mSecondPhone).getServiceStateTracker(); doReturn(mServiceStateStats).when(mSST).getServiceStateStats(); } Loading Loading @@ -414,66 +405,4 @@ public class MetricsCollectorTest extends TelephonyTest { assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS); // TODO(b/153196254): verify atom contents } @Test @SmallTest public void onPullAtom_perSimStatus() throws Exception { // Make PhoneFactory.getPhones() return an array of two replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mSecondPhone}); // phone 0 setup doReturn(0).when(mPhone).getPhoneId(); doReturn(1).when(mPhone).getSubId(); doReturn(100).when(mPhone).getCarrierId(); doReturn("6506953210") .when(mSubscriptionController) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC); doReturn("") .when(mSubscriptionController) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER); doReturn("+16506953210") .when(mSubscriptionController) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS); SubscriptionInfo subscriptionInfo1 = mock(SubscriptionInfo.class); doReturn("us").when(subscriptionInfo1).getCountryIso(); doReturn(subscriptionInfo1).when(mSubscriptionController).getSubscriptionInfo(1); // phone 1 setup doReturn(1).when(mSecondPhone).getPhoneId(); doReturn(2).when(mSecondPhone).getSubId(); doReturn(101).when(mSecondPhone).getCarrierId(); doReturn("0123") .when(mSubscriptionController) .getPhoneNumber(2, PHONE_NUMBER_SOURCE_UICC); doReturn("16506950123") .when(mSubscriptionController) .getPhoneNumber(2, PHONE_NUMBER_SOURCE_CARRIER); doReturn("+16506950123") .when(mSubscriptionController) .getPhoneNumber(2, PHONE_NUMBER_SOURCE_IMS); SubscriptionInfo subscriptionInfo2 = mock(SubscriptionInfo.class); doReturn("us").when(subscriptionInfo2).getCountryIso(); doReturn(subscriptionInfo2).when(mSubscriptionController).getSubscriptionInfo(2); List<StatsEvent> actualAtoms = new ArrayList<>(); int result = mMetricsCollector.onPullAtom(PER_SIM_STATUS, actualAtoms); verify(mTelephonyStatsLog).buildStatsEvent( PER_SIM_STATUS, 0, 100, 1, 0, 1); verify(mTelephonyStatsLog).buildStatsEvent( PER_SIM_STATUS, 1, 101, 1, 2, 2); assertThat(actualAtoms).hasSize(2); assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS); } @Test @SmallTest public void onPullAtom_perSimStatus_noSubscriptionController_skip() throws Exception { // Make SubscriptionController.getInstance() return null replaceInstance(SubscriptionController.class, "sInstance", null, null); List<StatsEvent> actualAtoms = new ArrayList<>(); int result = mMetricsCollector.onPullAtom(PER_SIM_STATUS, actualAtoms); assertThat(actualAtoms).isEmpty(); assertThat(result).isEqualTo(StatsManager.PULL_SKIP); } } Loading
src/java/com/android/internal/telephony/metrics/MetricsCollector.java +23 −57 Original line number Diff line number Diff line Loading @@ -16,10 +16,6 @@ package com.android.internal.telephony.metrics; import static android.telephony.PhoneNumberUtils.areSamePhoneNumber; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_IMS; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_UICC; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.SECOND_IN_MILLIS; Loading Loading @@ -55,14 +51,11 @@ import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSIO import android.annotation.Nullable; import android.app.StatsManager; import android.content.Context; import android.telephony.SubscriptionInfo; import android.text.TextUtils; import android.util.StatsEvent; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyStatsLog; import com.android.internal.telephony.imsphone.ImsPhone; import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; Loading Loading @@ -94,7 +87,6 @@ import com.android.telephony.Rlog; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; Loading Loading @@ -137,23 +129,20 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { .build(); private final PersistAtomsStorage mStorage; private final TelephonyStatsLogHelper mTelephonyStatsLog; private final StatsManager mStatsManager; private final AirplaneModeStats mAirplaneModeStats; private final Set<DataCallSessionStats> mOngoingDataCallStats = ConcurrentHashMap.newKeySet(); private static final Random sRandom = new Random(); public MetricsCollector(Context context) { this(context, new PersistAtomsStorage(context), new TelephonyStatsLogHelper()); this(context, new PersistAtomsStorage(context)); } /** Allows dependency injection. Used during unit tests. */ @VisibleForTesting public MetricsCollector(Context context, PersistAtomsStorage storage, TelephonyStatsLogHelper telephonyStatsLog) { PersistAtomsStorage storage) { mStorage = storage; mTelephonyStatsLog = telephonyStatsLog; mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER); if (mStatsManager != null) { registerAtom(CELLULAR_DATA_SERVICE_SWITCH, POLICY_PULL_DAILY); Loading Loading @@ -656,56 +645,33 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { } private int pullPerSimStatus(List<StatsEvent> data) { SubscriptionController subscriptionController = SubscriptionController.getInstance(); if (subscriptionController == null) { return StatsManager.PULL_SKIP; } int result = StatsManager.PULL_SKIP; for (Phone phone : getPhonesIfAny()) { int subId = phone.getSubId(); String countryIso = Optional.ofNullable(subscriptionController.getSubscriptionInfo(subId)) .map(SubscriptionInfo::getCountryIso) .orElse(""); // number[]- hone numbers from each sources: // numberState[] - int representation for each number source used in the atom // index 0 - PHONE_NUMBER_SOURCE_UICC // index 1 - PHONE_NUMBER_SOURCE_CARRIER // index 2 - PHONE_NUMBER_SOURCE_IMS String[] number = new String[] { subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_UICC), // 0 subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_CARRIER), // 1 subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_IMS), // 2 }; int[] numberState = new int[number.length]; // default value 0 for (int i = 0, stateForNextUniqueNumber = 1; i < numberState.length; i++) { if (TextUtils.isEmpty(number[i])) { // keep state 0 if number not available PerSimStatus perSimStatus = PerSimStatus.getCurrentState(phone); if (perSimStatus == null) { continue; } // the number is available: // try to find the same number from other sources and reuse the state for (int j = 0; j < i; j++) { if (!TextUtils.isEmpty(number[j]) && areSamePhoneNumber(number[i], number[j], countryIso)) { numberState[i] = numberState[j]; } } // didn't find same number (otherwise should not be state 0), assign a new state if (numberState[i] == 0) { numberState[i] = stateForNextUniqueNumber; stateForNextUniqueNumber++; } } StatsEvent statsEvent = mTelephonyStatsLog.buildStatsEvent( StatsEvent statsEvent = TelephonyStatsLog.buildStatsEvent( PER_SIM_STATUS, phone.getPhoneId(), // simSlotIndex phone.getCarrierId(), // carrierId numberState[0], // phoneNumberSourceUicc numberState[1], // phoneNumberSourceCarrier numberState[2]); // phoneNumberSourceIms perSimStatus.carrierId, // carrierId perSimStatus.phoneNumberSourceUicc, // phoneNumberSourceUicc perSimStatus.phoneNumberSourceCarrier, // phoneNumberSourceCarrier perSimStatus.phoneNumberSourceIms, // phoneNumberSourceIms perSimStatus.advancedCallingSettingEnabled, // volteEnabled perSimStatus.voWiFiSettingEnabled, // wfcEnabled perSimStatus.voWiFiModeSetting, // wfcMode perSimStatus.voWiFiRoamingModeSetting, // wfcRoamingMode perSimStatus.vtSettingEnabled, // videoCallingEnabled perSimStatus.dataRoamingEnabled, // dataRoamingEnabled perSimStatus.preferredNetworkType, // allowedNetworksByUser perSimStatus.disabled2g, // is2gDisabled false, // TODO(b/215758472): isPin1Enabled 0); // TODO(b/215758472): simVoltageClass data.add(statsEvent); result = StatsManager.PULL_SUCCESS; } return StatsManager.PULL_SUCCESS; return result; } /** Registers a pulled atom ID {@code atomId} with optional {@code policy} for pulling. */ Loading
src/java/com/android/internal/telephony/metrics/PerSimStatus.java 0 → 100644 +199 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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 android.telephony.PhoneNumberUtils.areSamePhoneNumber; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_IMS; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_UICC; import static com.android.internal.telephony.TelephonyStatsLog.PER_SIM_STATUS__WFC_MODE__CELLULAR_PREFERRED; import static com.android.internal.telephony.TelephonyStatsLog.PER_SIM_STATUS__WFC_MODE__UNKNOWN; import static com.android.internal.telephony.TelephonyStatsLog.PER_SIM_STATUS__WFC_MODE__WIFI_ONLY; import static com.android.internal.telephony.TelephonyStatsLog.PER_SIM_STATUS__WFC_MODE__WIFI_PREFERRED; import android.annotation.Nullable; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; import android.telephony.ims.ImsManager; import android.telephony.ims.ImsMmTelManager; import android.text.TextUtils; import com.android.internal.telephony.Phone; import com.android.internal.telephony.SubscriptionController; import java.util.Optional; /** Stores the per SIM status. */ public class PerSimStatus { private static final long BITMASK_2G = TelephonyManager.NETWORK_TYPE_BITMASK_GSM | TelephonyManager.NETWORK_TYPE_BITMASK_GPRS | TelephonyManager.NETWORK_TYPE_BITMASK_EDGE | TelephonyManager.NETWORK_TYPE_BITMASK_CDMA | TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT; public final int carrierId; public final int phoneNumberSourceUicc; public final int phoneNumberSourceCarrier; public final int phoneNumberSourceIms; public final boolean advancedCallingSettingEnabled; public final boolean voWiFiSettingEnabled; public final int voWiFiModeSetting; public final int voWiFiRoamingModeSetting; public final boolean vtSettingEnabled; public final boolean dataRoamingEnabled; public final long preferredNetworkType; public final boolean disabled2g; /** Returns the current sim status of the given {@link Phone}. */ @Nullable public static PerSimStatus getCurrentState(Phone phone) { int[] numberState = getNumberState(phone); if (numberState == null) return null; ImsMmTelManager imsMmTelManager = getImsMmTelManager(phone); return new PerSimStatus( phone.getCarrierId(), numberState[0], numberState[1], numberState[2], imsMmTelManager == null ? false : imsMmTelManager.isAdvancedCallingSettingEnabled(), imsMmTelManager == null ? false : imsMmTelManager.isVoWiFiSettingEnabled(), imsMmTelManager == null ? PER_SIM_STATUS__WFC_MODE__UNKNOWN : wifiCallingModeToProtoEnum(imsMmTelManager.getVoWiFiModeSetting()), imsMmTelManager == null ? PER_SIM_STATUS__WFC_MODE__UNKNOWN : wifiCallingModeToProtoEnum(imsMmTelManager.getVoWiFiRoamingModeSetting()), imsMmTelManager == null ? false : imsMmTelManager.isVtSettingEnabled(), phone.getDataRoamingEnabled(), phone.getAllowedNetworkTypes(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER), is2gDisabled(phone)); } private PerSimStatus( int carrierId, int phoneNumberSourceUicc, int phoneNumberSourceCarrier, int phoneNumberSourceIms, boolean advancedCallingSettingEnabled, boolean voWiFiSettingEnabled, int voWiFiModeSetting, int voWiFiRoamingModeSetting, boolean vtSettingEnabled, boolean dataRoamingEnabled, long preferredNetworkType, boolean disabled2g) { this.carrierId = carrierId; this.phoneNumberSourceUicc = phoneNumberSourceUicc; this.phoneNumberSourceCarrier = phoneNumberSourceCarrier; this.phoneNumberSourceIms = phoneNumberSourceIms; this.advancedCallingSettingEnabled = advancedCallingSettingEnabled; this.voWiFiSettingEnabled = voWiFiSettingEnabled; this.voWiFiModeSetting = voWiFiModeSetting; this.voWiFiRoamingModeSetting = voWiFiRoamingModeSetting; this.vtSettingEnabled = vtSettingEnabled; this.dataRoamingEnabled = dataRoamingEnabled; this.preferredNetworkType = preferredNetworkType; this.disabled2g = disabled2g; } @Nullable private static ImsMmTelManager getImsMmTelManager(Phone phone) { ImsManager imsManager = phone.getContext().getSystemService(ImsManager.class); if (imsManager == null) { return null; } try { return imsManager.getImsMmTelManager(phone.getSubId()); } catch (IllegalArgumentException e) { return null; // Invalid subId } } /** * Returns an array containing the various phone numbers available on the device. * <ul> * <li>Index 0: phone number associated with {@code PHONE_NUMBER_SOURCE_UICC}.</li> * <li>Index 1: phone number associated with {@code PHONE_NUMBER_SOURCE_CARRIER}.</li> * <li>Index 2: phone number associated with {@code PHONE_NUMBER_SOURCE_IMS}.</li> * </ul> */ @Nullable private static int[] getNumberState(Phone phone) { SubscriptionController subscriptionController = SubscriptionController.getInstance(); if (subscriptionController == null) { return null; } int subId = phone.getSubId(); String countryIso = Optional.ofNullable(subscriptionController.getSubscriptionInfo(subId)) .map(SubscriptionInfo::getCountryIso) .orElse(""); // number[] - hone numbers from each sources: String[] number = new String[] { subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_UICC), // 0 subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_CARRIER), // 1 subscriptionController.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_IMS), // 2 }; int[] numberState = new int[number.length]; // default value 0 for (int i = 0, stateForNextUniqueNumber = 1; i < numberState.length; i++) { if (TextUtils.isEmpty(number[i])) { // keep state 0 if number not available continue; } // the number is available: // try to find the same number from other sources and reuse the state for (int j = 0; j < i; j++) { if (!TextUtils.isEmpty(number[j]) && areSamePhoneNumber(number[i], number[j], countryIso)) { numberState[i] = numberState[j]; } } // didn't find same number (otherwise should not be state 0), assign a new state if (numberState[i] == 0) { numberState[i] = stateForNextUniqueNumber++; } } return numberState; } /** * Returns {@code true} if 2G cellular network is disabled (Allow 2G toggle in the settings). */ private static boolean is2gDisabled(Phone phone) { return (phone.getAllowedNetworkTypes( TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G) & BITMASK_2G) == 0; } /** Converts {@link ImsMmTelManager.WifiCallingMode} to the value of PerSimStatus WfcMode. */ private static int wifiCallingModeToProtoEnum(@ImsMmTelManager.WiFiCallingMode int mode) { switch (mode) { case ImsMmTelManager.WIFI_MODE_WIFI_ONLY: return PER_SIM_STATUS__WFC_MODE__WIFI_ONLY; case ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED: return PER_SIM_STATUS__WFC_MODE__CELLULAR_PREFERRED; case ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED: return PER_SIM_STATUS__WFC_MODE__WIFI_PREFERRED; default: return PER_SIM_STATUS__WFC_MODE__UNKNOWN; } } }
src/java/com/android/internal/telephony/metrics/TelephonyStatsLogHelper.javadeleted 100644 → 0 +0 −47 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.util.StatsEvent; import com.android.internal.telephony.TelephonyStatsLog; /** * Wrapper of generated TelephonyStatsLog class. Can be mocked during unit test. * * <p>Methods here have the same name/arguments/return as their counterparts in TelephonyStatsLog * and simply call their counterparts. Do not put any involved logic here as they cannot be * covered by unit test. */ public class TelephonyStatsLogHelper { /** Wraps TelephonyStatsLog.buildStatsEvent(...) for atom PER_SIM_STATUS. */ public StatsEvent buildStatsEvent( int atomId, int simSlotIndex, int carrierId, int phoneNumberSourceUicc, int phoneNumberSourceCarrier, int phoneNumberSourceIms) { return TelephonyStatsLog.buildStatsEvent( atomId, simSlotIndex, carrierId, phoneNumberSourceUicc, phoneNumberSourceCarrier, phoneNumberSourceIms); } }
tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java +3 −0 Original line number Diff line number Diff line Loading @@ -287,6 +287,8 @@ public class ContextFixture implements TestFixture<Context> { return mPowerWhitelistManager; case Context.LOCATION_SERVICE: return mLocationManager; case Context.TELEPHONY_IMS_SERVICE: return mImsManager; default: return null; } Loading Loading @@ -701,6 +703,7 @@ public class ContextFixture implements TestFixture<Context> { private final ContentProvider mContentProvider = spy(new FakeContentProvider()); private final ImsManager mImsManager = mock(ImsManager.class); private final Configuration mConfiguration = new Configuration(); private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); private final SharedPreferences mSharedPreferences = PreferenceManager Loading
tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java +1 −72 Original line number Diff line number Diff line Loading @@ -16,13 +16,8 @@ package com.android.internal.telephony.metrics; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_IMS; import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_UICC; 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.PER_SIM_STATUS; import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE; import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY; import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE; Loading @@ -33,20 +28,17 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.app.StatsManager; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.util.StatsEvent; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch; import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState; Loading Loading @@ -91,7 +83,6 @@ public class MetricsCollectorTest extends TelephonyTest { // b/153195691: we cannot verify the contents of StatsEvent as its getters are marked with @hide @Mock private Phone mSecondPhone; @Mock private TelephonyStatsLogHelper mTelephonyStatsLog; @Mock private UiccSlot mPhysicalSlot; @Mock private UiccSlot mEsimSlot; @Mock private UiccCard mActiveCard; Loading @@ -105,7 +96,7 @@ public class MetricsCollectorTest extends TelephonyTest { public void setUp() throws Exception { super.setUp(getClass().getSimpleName()); mMetricsCollector = new MetricsCollector(mContext, mPersistAtomsStorage, mTelephonyStatsLog); new MetricsCollector(mContext, mPersistAtomsStorage); doReturn(mSST).when(mSecondPhone).getServiceStateTracker(); doReturn(mServiceStateStats).when(mSST).getServiceStateStats(); } Loading Loading @@ -414,66 +405,4 @@ public class MetricsCollectorTest extends TelephonyTest { assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS); // TODO(b/153196254): verify atom contents } @Test @SmallTest public void onPullAtom_perSimStatus() throws Exception { // Make PhoneFactory.getPhones() return an array of two replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mSecondPhone}); // phone 0 setup doReturn(0).when(mPhone).getPhoneId(); doReturn(1).when(mPhone).getSubId(); doReturn(100).when(mPhone).getCarrierId(); doReturn("6506953210") .when(mSubscriptionController) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC); doReturn("") .when(mSubscriptionController) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER); doReturn("+16506953210") .when(mSubscriptionController) .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS); SubscriptionInfo subscriptionInfo1 = mock(SubscriptionInfo.class); doReturn("us").when(subscriptionInfo1).getCountryIso(); doReturn(subscriptionInfo1).when(mSubscriptionController).getSubscriptionInfo(1); // phone 1 setup doReturn(1).when(mSecondPhone).getPhoneId(); doReturn(2).when(mSecondPhone).getSubId(); doReturn(101).when(mSecondPhone).getCarrierId(); doReturn("0123") .when(mSubscriptionController) .getPhoneNumber(2, PHONE_NUMBER_SOURCE_UICC); doReturn("16506950123") .when(mSubscriptionController) .getPhoneNumber(2, PHONE_NUMBER_SOURCE_CARRIER); doReturn("+16506950123") .when(mSubscriptionController) .getPhoneNumber(2, PHONE_NUMBER_SOURCE_IMS); SubscriptionInfo subscriptionInfo2 = mock(SubscriptionInfo.class); doReturn("us").when(subscriptionInfo2).getCountryIso(); doReturn(subscriptionInfo2).when(mSubscriptionController).getSubscriptionInfo(2); List<StatsEvent> actualAtoms = new ArrayList<>(); int result = mMetricsCollector.onPullAtom(PER_SIM_STATUS, actualAtoms); verify(mTelephonyStatsLog).buildStatsEvent( PER_SIM_STATUS, 0, 100, 1, 0, 1); verify(mTelephonyStatsLog).buildStatsEvent( PER_SIM_STATUS, 1, 101, 1, 2, 2); assertThat(actualAtoms).hasSize(2); assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS); } @Test @SmallTest public void onPullAtom_perSimStatus_noSubscriptionController_skip() throws Exception { // Make SubscriptionController.getInstance() return null replaceInstance(SubscriptionController.class, "sInstance", null, null); List<StatsEvent> actualAtoms = new ArrayList<>(); int result = mMetricsCollector.onPullAtom(PER_SIM_STATUS, actualAtoms); assertThat(actualAtoms).isEmpty(); assertThat(result).isEqualTo(StatsManager.PULL_SKIP); } }