diff options
author | Xin Li <delphij@google.com> | 2023-12-08 13:14:14 -0800 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2023-12-08 13:14:14 -0800 |
commit | d4ebe427e4393934824153f441d00f9873bc2820 (patch) | |
tree | 116aa4442cf904d091dbba38984d8d47ca2dcfb5 | |
parent | 6ea2d70537a9a0ad76503c91c780ba442b0da8bb (diff) | |
parent | cb2e4328ea290a5aa19c06992fb5166d368fab54 (diff) | |
download | telephony-d4ebe427e4393934824153f441d00f9873bc2820.tar.gz |
Merge Android 14 QPR1
Merged-In: I1fbd86e4d1ba15fffe7da32728b2b0ed489e20c1
Bug: 315507370
Change-Id: Ie301244cfa8b3b99a83496794131d4a5ce60b619
75 files changed, 6063 insertions, 1118 deletions
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto index 61e44a3c8f..2e569f042c 100644 --- a/proto/src/persist_atoms.proto +++ b/proto/src/persist_atoms.proto @@ -275,6 +275,8 @@ message VoiceCallSession { optional int32 call_duration = 32; optional int32 last_known_rat = 33; optional int32 fold_state = 34; + optional int64 rat_switch_count_after_connected = 35; + optional bool handover_in_progress = 36; // Internal use only optional int64 setup_begin_millis = 10001; @@ -378,6 +380,8 @@ message CellularServiceState { optional bool is_emergency_only = 10; optional bool is_internet_pdn_up = 11; optional int32 fold_state = 12; + optional bool override_voice_service = 13; + optional bool isDataEnabled = 14; // Internal use only optional int64 last_used_millis = 10001; diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java index 6b44998db3..93f5ab01e7 100644 --- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java +++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java @@ -116,14 +116,15 @@ public class CarrierServiceStateTracker extends Handler { mPhone.getContext(), mPhone.getSubId(), CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT, - CarrierConfigManager - .KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT); + CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, + CarrierConfigManager.KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL); if (b.isEmpty()) return; for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) { NotificationType notificationType = entry.getValue(); notificationType.setDelay(b); + notificationType.setEnabled(b); } handleConfigChanges(); }); @@ -430,6 +431,18 @@ public class CarrierServiceStateTracker extends Handler { void setDelay(PersistableBundle bundle); /** + * Checks whether this Notification is enabled. + * @return {@code true} if this Notification is enabled, false otherwise + */ + boolean isEnabled(); + + /** + * Sets whether this Notification is enabled. If disabled, it will not build notification. + * @param bundle PersistableBundle + */ + void setEnabled(PersistableBundle bundle); + + /** * returns notification type id. **/ int getTypeId(); @@ -458,6 +471,7 @@ public class CarrierServiceStateTracker extends Handler { private final int mTypeId; private int mDelay = UNINITIALIZED_DELAY_VALUE; + private boolean mEnabled = false; PrefNetworkNotification(int typeId) { this.mTypeId = typeId; @@ -480,6 +494,28 @@ public class CarrierServiceStateTracker extends Handler { return mDelay; } + /** + * Checks whether this Notification is enabled. + * @return {@code true} if this Notification is enabled, false otherwise + */ + public boolean isEnabled() { + return mEnabled; + } + + /** + * Sets whether this Notification is enabled. If disabled, it will not build notification. + * @param bundle PersistableBundle + */ + public void setEnabled(PersistableBundle bundle) { + if (bundle == null) { + Rlog.e(LOG_TAG, "bundle is null"); + return; + } + mEnabled = !bundle.getBoolean( + CarrierConfigManager.KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL); + Rlog.i(LOG_TAG, "reading enabled notification pref network: " + mEnabled); + } + public int getTypeId() { return mTypeId; } @@ -497,10 +533,10 @@ public class CarrierServiceStateTracker extends Handler { */ public boolean sendMessage() { Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: " - + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode() - + "," + mSST.isRadioOn()); - if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneStillRegistered() || isGlobalMode() - || isRadioOffOrAirplaneMode()) { + + "," + mEnabled + "," + isPhoneStillRegistered() + "," + mDelay + + "," + isGlobalMode() + "," + mSST.isRadioOn()); + if (!mEnabled || mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneStillRegistered() + || isGlobalMode() || isRadioOffOrAirplaneMode()) { return false; } return true; @@ -559,6 +595,22 @@ public class CarrierServiceStateTracker extends Handler { return mDelay; } + /** + * Checks whether this Notification is enabled. + * @return {@code true} if this Notification is enabled, false otherwise + */ + public boolean isEnabled() { + return true; + } + + /** + * Sets whether this Notification is enabled. If disabled, it will not build notification. + * @param bundle PersistableBundle + */ + public void setEnabled(PersistableBundle bundle) { + // always allowed. There is no config to hide notifications. + } + public int getTypeId() { return mTypeId; } diff --git a/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java b/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java index 82d44096a8..7e8663a837 100644 --- a/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java +++ b/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java @@ -26,6 +26,8 @@ import android.telephony.CellBroadcastIdRange; import android.telephony.SmsCbMessage; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.util.IndentingPrintWriter; +import android.util.LocalLog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo; @@ -33,6 +35,8 @@ import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -59,6 +63,7 @@ public final class CellBroadcastConfigTracker extends StateMachine { // Cache of current cell broadcast id ranges of 3gpp2 private List<CellBroadcastIdRange> mCbRanges3gpp2 = new CopyOnWriteArrayList<>(); private Phone mPhone; + private final LocalLog mLocalLog = new LocalLog(128); @VisibleForTesting public int mSubId; @VisibleForTesting @@ -106,8 +111,7 @@ public final class CellBroadcastConfigTracker extends StateMachine { @Override public String toString() { return "Request[mCbRangesRequest3gpp = " + mCbRangesRequest3gpp + ", " - + "mCbRangesRequest3gpp2 = " + mCbRangesRequest3gpp2 + ", " - + "mCallback = " + mCallback + "]"; + + "mCbRangesRequest3gpp2 = " + mCbRangesRequest3gpp2 + "]"; } } @@ -175,6 +179,9 @@ public final class CellBroadcastConfigTracker extends StateMachine { Request request = (Request) msg.obj; if (DBG) { logd("IdleState handle EVENT_REQUEST with request:" + request); + mLocalLog.log("IdleState handle EVENT_REQUEST with request:" + request + + ", mCbRanges3gpp:" + mCbRanges3gpp + + ", mCbRanges3gpp2:" + mCbRanges3gpp2); } if (!mCbRanges3gpp.equals(request.get3gppRanges())) { // set gsm config if the config is changed @@ -229,6 +236,7 @@ public final class CellBroadcastConfigTracker extends StateMachine { transitionTo(mGsmActivatingState); } else { logd("Failed to set gsm config"); + mLocalLog.log("GsmConfiguringState Failed to set gsm config:" + request); request.getCallback().accept( TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG); // transit to idle state on the failure case @@ -265,6 +273,8 @@ public final class CellBroadcastConfigTracker extends StateMachine { if (DBG) { logd("GsmActivatingState handle EVENT_ACTIVATION_DONE with request:" + request); + mLocalLog.log("GsmActivatingState EVENT_ACTIVATION_DONE, exception:" + + ar.exception + ", request:" + request); } if (ar.exception == null) { mCbRanges3gpp = request.get3gppRanges(); @@ -326,6 +336,7 @@ public final class CellBroadcastConfigTracker extends StateMachine { transitionTo(mCdmaActivatingState); } else { logd("Failed to set cdma config"); + mLocalLog.log("CdmaConfiguringState Failed to set cdma config:" + request); request.getCallback().accept( TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG); // transit to idle state on the failure case @@ -362,6 +373,8 @@ public final class CellBroadcastConfigTracker extends StateMachine { if (DBG) { logd("CdmaActivatingState handle EVENT_ACTIVATION_DONE with request:" + request); + mLocalLog.log("CdmaActivatingState EVENT_ACTIVATION_DONE, exception:" + + ar.exception + ", request:" + request); } if (ar.exception == null) { mCbRanges3gpp2 = request.get3gpp2Ranges(); @@ -531,4 +544,26 @@ public final class CellBroadcastConfigTracker extends StateMachine { mPhone.mCi.setCdmaBroadcastActivation(activate, response); } } + + /** + * Dump the state of CellBroadcastConfigTracker + * + * @param fd File descriptor + * @param printWriter Print writer + * @param args Arguments + */ + public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + pw.println(CellBroadcastConfigTracker.class.getSimpleName() + + "-" + mPhone.getPhoneId() + ":"); + pw.increaseIndent(); + pw.println("Current mCbRanges3gpp:" + mCbRanges3gpp); + pw.println("Current mCbRanges3gpp2:" + mCbRanges3gpp2); + pw.decreaseIndent(); + + pw.println("Local logs:"); + pw.increaseIndent(); + mLocalLog.dump(fd, pw, args); + pw.decreaseIndent(); + } } diff --git a/src/java/com/android/internal/telephony/GbaManager.java b/src/java/com/android/internal/telephony/GbaManager.java index b1db1ac353..7c5f636d2b 100644 --- a/src/java/com/android/internal/telephony/GbaManager.java +++ b/src/java/com/android/internal/telephony/GbaManager.java @@ -40,6 +40,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.metrics.RcsStats; import com.android.telephony.Rlog; +import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentLinkedQueue; /** @@ -155,7 +156,11 @@ public class GbaManager { public synchronized void unlinkToDeath() { if (mBinder != null) { - mBinder.unlinkToDeath(this, 0); + try { + mBinder.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + // do nothing + } mBinder = null; } } diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java index 5eae06112c..6e2601e635 100644 --- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java +++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java @@ -249,6 +249,7 @@ public class GsmCdmaPhone extends Phone { private String mImeiSv; private String mVmNumber; private int mImeiType = IMEI_TYPE_UNKNOWN; + private int mSimState = TelephonyManager.SIM_STATE_UNKNOWN; @VisibleForTesting public CellBroadcastConfigTracker mCellBroadcastConfigTracker = @@ -426,9 +427,9 @@ public class GsmCdmaPhone extends Phone { if (mPhoneId == intent.getIntExtra( SubscriptionManager.EXTRA_SLOT_INDEX, SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { - int simState = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE, + mSimState = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_UNKNOWN); - if (simState == TelephonyManager.SIM_STATE_LOADED + if (mSimState == TelephonyManager.SIM_STATE_LOADED && currentSlotSubIdChanged()) { setNetworkSelectionModeAutomatic(null); } @@ -680,6 +681,7 @@ public class GsmCdmaPhone extends Phone { mTelecomVoiceServiceStateOverride = newOverride; if (changed && mSST != null) { mSST.onTelecomVoiceServiceStateOverrideChanged(); + mSST.getServiceStateStats().onVoiceServiceStateOverrideChanged(hasService); } } @@ -3521,13 +3523,14 @@ public class GsmCdmaPhone extends Phone { case EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE: logd("EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE"); ar = (AsyncResult) msg.obj; + // Only test for a success here in order to flip the support flag. + // Testing for the negative case, e.g. REQUEST_NOT_SUPPORTED, is insufficient + // because the modem or the RIL could still return exceptions for temporary + // failures even when the feature is unsupported. if (ar == null || ar.exception == null) { mIsNullCipherAndIntegritySupported = true; return; } - CommandException.Error error = ((CommandException) ar.exception).getCommandError(); - mIsNullCipherAndIntegritySupported = !error.equals( - CommandException.Error.REQUEST_NOT_SUPPORTED); break; case EVENT_IMS_DEREGISTRATION_TRIGGERED: @@ -4499,6 +4502,12 @@ public class GsmCdmaPhone extends Phone { e.printStackTrace(); } pw.flush(); + try { + mCellBroadcastConfigTracker.dump(fd, pw, args); + } catch (Exception e) { + e.printStackTrace(); + } + pw.flush(); } @Override @@ -5017,6 +5026,10 @@ public class GsmCdmaPhone extends Phone { return; } + if (mSimState != TelephonyManager.SIM_STATE_LOADED) { + return; + } + if (config == null) { loge("didn't get the vonr_enabled_bool from the carrier config."); return; diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java index 90885fe6e3..9b116b4228 100644 --- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java +++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java @@ -657,7 +657,6 @@ public class ImsSmsDispatcher extends SMSDispatcher { @VisibleForTesting public void fallbackToPstn(SmsTracker tracker) { - tracker.mMessageRef = nextMessageRef(); mSmsDispatchersController.sendRetrySms(tracker); } diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java index de854fa7ca..076b00197f 100644 --- a/src/java/com/android/internal/telephony/LocaleTracker.java +++ b/src/java/com/android/internal/telephony/LocaleTracker.java @@ -372,7 +372,10 @@ public class LocaleTracker extends Handler { */ public void updateOperatorNumeric(String operatorNumeric) { if (TextUtils.isEmpty(operatorNumeric)) { - sendMessageDelayed(obtainMessage(EVENT_OPERATOR_LOST), SERVICE_OPERATOR_LOST_DELAY_MS); + if (!hasMessages(EVENT_OPERATOR_LOST)) { + sendMessageDelayed(obtainMessage(EVENT_OPERATOR_LOST), + SERVICE_OPERATOR_LOST_DELAY_MS); + } } else { removeMessages(EVENT_OPERATOR_LOST); updateOperatorNumericImmediate(operatorNumeric); @@ -528,6 +531,9 @@ public class LocaleTracker extends Handler { if (!mPhone.isRadioOn()) { countryIso = ""; countryIsoDebugInfo = "radio off"; + + // clear cell infos, we don't know where the next network to camp on. + mCellInfoList = null; } log("updateLocale: countryIso = " + countryIso diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java index 0acae4b56d..d6b09303be 100644 --- a/src/java/com/android/internal/telephony/MultiSimSettingController.java +++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java @@ -83,7 +83,8 @@ public class MultiSimSettingController extends Handler { private static final int EVENT_SUBSCRIPTION_INFO_CHANGED = 4; private static final int EVENT_SUBSCRIPTION_GROUP_CHANGED = 5; private static final int EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED = 6; - private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 8; + @VisibleForTesting + public static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 8; @VisibleForTesting public static final int EVENT_RADIO_STATE_CHANGED = 9; @@ -150,6 +151,8 @@ public class MultiSimSettingController extends Handler { // The number of existing DataSettingsControllerCallback private int mCallbacksCount; + /** The number of active modem count. */ + private int mActiveModemCount; private static final String SETTING_USER_PREF_DATA_SUB = "user_preferred_data_sub"; @@ -163,15 +166,13 @@ public class MultiSimSettingController extends Handler { } @Override - public void onDataEnabledChanged(boolean enabled, - @TelephonyManager.DataEnabledChangedReason int reason, String callingPackage) { + public void onUserDataEnabledChanged(boolean enabled, @NonNull String callingPackage) { int subId = mPhone.getSubId(); - // notifyUserDataEnabled if the change is called from external and reason is - // DATA_ENABLED_REASON_USER + // only notifyUserDataEnabled if the change is called from external to avoid + // setUserDataEnabledForGroup infinite loop if (SubscriptionManager.isValidSubscriptionId(subId) - && reason == TelephonyManager.DATA_ENABLED_REASON_USER && !getInstance().mContext.getOpPackageName().equals(callingPackage)) { - getInstance().notifyUserDataEnabled(mPhone.getSubId(), enabled); + getInstance().notifyUserDataEnabled(subId, enabled); } } @@ -217,11 +218,14 @@ public class MultiSimSettingController extends Handler { mSubscriptionManagerService = SubscriptionManagerService.getInstance(); // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change. - final int phoneCount = ((TelephonyManager) mContext.getSystemService( - Context.TELEPHONY_SERVICE)).getSupportedModemCount(); + TelephonyManager telephonyManager = ((TelephonyManager) mContext.getSystemService( + TelephonyManager.class)); + final int phoneCount = telephonyManager.getSupportedModemCount(); mCarrierConfigLoadedSubIds = new int[phoneCount]; Arrays.fill(mCarrierConfigLoadedSubIds, SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mActiveModemCount = telephonyManager.getActiveModemCount(); + PhoneConfigurationManager.registerForMultiSimConfigChange( this, EVENT_MULTI_SIM_CONFIG_CHANGED, null); @@ -316,9 +320,14 @@ public class MultiSimSettingController extends Handler { onMultiSimConfigChanged(activeModems); break; case EVENT_RADIO_STATE_CHANGED: - for (Phone phone : PhoneFactory.getPhones()) { - if (phone.mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE) { - if (DBG) log("Radio unavailable. Clearing sub info initialized flag."); + for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) { + Phone phone = PhoneFactory.getPhone(phoneId); + if (phone != null && phone.mCi.getRadioState() + == TelephonyManager.RADIO_POWER_UNAVAILABLE) { + if (DBG) { + log("Radio unavailable on phone " + phoneId + + ", clearing sub info initialized flag"); + } mSubInfoInitialized = false; break; } @@ -453,6 +462,8 @@ public class MultiSimSettingController extends Handler { } private void onMultiSimConfigChanged(int activeModems) { + mActiveModemCount = activeModems; + log("onMultiSimConfigChanged: current ActiveModemCount=" + mActiveModemCount); // Clear mCarrierConfigLoadedSubIds. Other actions will responds to active // subscription change. for (int phoneId = activeModems; phoneId < mCarrierConfigLoadedSubIds.length; phoneId++) { @@ -601,8 +612,7 @@ public class MultiSimSettingController extends Handler { // opportunistic subscription active (activeSubInfos.size() > 1), we automatically // set the primary to be default SIM and return. if (mPrimarySubList.size() == 1 && (change != PRIMARY_SUB_REMOVED - || ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) - .getActiveModemCount() == 1)) { + || mActiveModemCount == 1)) { int subId = mPrimarySubList.get(0); if (DBG) log("updateDefaultValues: to only primary sub " + subId); mSubscriptionManagerService.setDefaultDataSubId(subId); @@ -1058,10 +1068,11 @@ public class MultiSimSettingController extends Handler { } private boolean isRadioAvailableOnAllSubs() { - for (Phone phone : PhoneFactory.getPhones()) { - if ((phone.mCi != null && - phone.mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE) || - phone.isShuttingDown()) { + for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) { + Phone phone = PhoneFactory.getPhone(phoneId); + if (phone != null + && (phone.mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE + || phone.isShuttingDown())) { return false; } } diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java index beebf2206e..d1c83590d3 100644 --- a/src/java/com/android/internal/telephony/NetworkTypeController.java +++ b/src/java/com/android/internal/telephony/NetworkTypeController.java @@ -29,6 +29,7 @@ import android.os.PowerManager; import android.telephony.AccessNetworkConstants; import android.telephony.Annotation; import android.telephony.CarrierConfigManager; +import android.telephony.CellInfo; import android.telephony.NetworkRegistrationInfo; import android.telephony.PhysicalChannelConfig; import android.telephony.ServiceState; @@ -40,7 +41,6 @@ import android.text.TextUtils; import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback; import com.android.internal.telephony.data.DataUtils; -import com.android.internal.telephony.util.ArrayUtils; import com.android.internal.util.IState; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.State; @@ -52,9 +52,11 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.IntStream; @@ -105,7 +107,7 @@ public class NetworkTypeController extends StateMachine { /** Event for preferred network mode changed. */ private static final int EVENT_PREFERRED_NETWORK_MODE_CHANGED = 10; /** Event for physical channel configs changed. */ - private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 11; + private static final int EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED = 11; /** Event for device idle mode changed, when device goes to deep sleep and pauses all timers. */ private static final int EVENT_DEVICE_IDLE_MODE_CHANGED = 12; @@ -123,13 +125,13 @@ public class NetworkTypeController extends StateMachine { sEvents[EVENT_RADIO_OFF_OR_UNAVAILABLE] = "EVENT_RADIO_OFF_OR_UNAVAILABLE"; sEvents[EVENT_PREFERRED_NETWORK_MODE_CHANGED] = "EVENT_PREFERRED_NETWORK_MODE_CHANGED"; sEvents[EVENT_INITIALIZE] = "EVENT_INITIALIZE"; - sEvents[EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED] = "EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED"; + sEvents[EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED] = "EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED"; sEvents[EVENT_DEVICE_IDLE_MODE_CHANGED] = "EVENT_DEVICE_IDLE_MODE_CHANGED"; } - private final @NonNull Phone mPhone; - private final @NonNull DisplayInfoController mDisplayInfoController; - private final @NonNull BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @NonNull private final Phone mPhone; + @NonNull private final DisplayInfoController mDisplayInfoController; + @NonNull private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { @@ -140,7 +142,7 @@ public class NetworkTypeController extends StateMachine { } }; - private final @NonNull CarrierConfigManager.CarrierConfigChangeListener + @NonNull private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener = new CarrierConfigManager.CarrierConfigChangeListener() { @Override @@ -153,9 +155,9 @@ public class NetworkTypeController extends StateMachine { } }; - private @NonNull Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>(); - private @NonNull String mLteEnhancedPattern = ""; - private @Annotation.OverrideNetworkType int mOverrideNetworkType; + @NonNull private Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>(); + @NonNull private String mLteEnhancedPattern = ""; + @Annotation.OverrideNetworkType private int mOverrideNetworkType; private boolean mIsPhysicalChannelConfigOn; private boolean mIsPrimaryTimerActive; private boolean mIsSecondaryTimerActive; @@ -163,11 +165,12 @@ public class NetworkTypeController extends StateMachine { private int mLtePlusThresholdBandwidth; private int mNrAdvancedThresholdBandwidth; private boolean mIncludeLteForNrAdvancedThresholdBandwidth; - private @NonNull int[] mAdditionalNrAdvancedBandsList; - private @NonNull String mPrimaryTimerState; - private @NonNull String mSecondaryTimerState; - private @NonNull String mPreviousState; - private @LinkStatus int mPhysicalLinkStatus; + private boolean mRatchetPccFieldsForSameAnchorNrCell; + @NonNull private final Set<Integer> mAdditionalNrAdvancedBands = new HashSet<>(); + @NonNull private String mPrimaryTimerState; + @NonNull private String mSecondaryTimerState; + @NonNull private String mPreviousState; + @LinkStatus private int mPhysicalLinkStatus; private boolean mIsPhysicalChannelConfig16Supported; private boolean mIsNrAdvancedAllowedByPco = false; private int mNrAdvancedCapablePcoId = 0; @@ -175,12 +178,17 @@ public class NetworkTypeController extends StateMachine { private boolean mEnableNrAdvancedWhileRoaming = true; private boolean mIsDeviceIdleMode = false; - private @Nullable DataNetworkControllerCallback mNrAdvancedCapableByPcoChangedCallback = null; - private @Nullable DataNetworkControllerCallback mNrPhysicalLinkStatusChangedCallback = null; + @Nullable private DataNetworkControllerCallback mNrAdvancedCapableByPcoChangedCallback = null; + @Nullable private DataNetworkControllerCallback mNrPhysicalLinkStatusChangedCallback = null; // Cached copies below to prevent race conditions - private @NonNull ServiceState mServiceState; - private @Nullable List<PhysicalChannelConfig> mPhysicalChannelConfigs; + @NonNull private ServiceState mServiceState; + @Nullable private List<PhysicalChannelConfig> mPhysicalChannelConfigs; + + // Ratchet physical channel config fields to prevent 5G/5G+ flickering + @NonNull private Set<Integer> mRatchetedNrBands = new HashSet<>(); + private int mRatchetedNrBandwidths = 0; + private int mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN; /** * NetworkTypeController constructor. @@ -246,7 +254,7 @@ public class NetworkTypeController extends StateMachine { mPhone.registerForPreferredNetworkTypeChanged(getHandler(), EVENT_PREFERRED_NETWORK_MODE_CHANGED, null); mPhone.registerForPhysicalChannelConfig(getHandler(), - EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED, null); + EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED, null); mPhone.getServiceStateTracker().registerForServiceStateChanged(getHandler(), EVENT_SERVICE_STATE_CHANGED, null); mIsPhysicalChannelConfig16Supported = mPhone.getContext().getSystemService( @@ -293,10 +301,16 @@ public class NetworkTypeController extends StateMachine { CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT); mIncludeLteForNrAdvancedThresholdBandwidth = config.getBoolean( CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL); + mRatchetPccFieldsForSameAnchorNrCell = config.getBoolean( + CarrierConfigManager.KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL); mEnableNrAdvancedWhileRoaming = config.getBoolean( CarrierConfigManager.KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL); - mAdditionalNrAdvancedBandsList = config.getIntArray( + mAdditionalNrAdvancedBands.clear(); + int[] additionalNrAdvancedBands = config.getIntArray( CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY); + if (additionalNrAdvancedBands != null) { + Arrays.stream(additionalNrAdvancedBands).forEach(mAdditionalNrAdvancedBands::add); + } mNrAdvancedCapablePcoId = config.getInt( CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT); if (mNrAdvancedCapablePcoId > 0 && mNrAdvancedCapableByPcoChangedCallback == null) { @@ -343,6 +357,7 @@ public class NetworkTypeController extends StateMachine { String overrideSecondaryTimerRule = config.getString( CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING); createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule); + updatePhysicalChannelConfigs(); } private void createTimerRules(String icons, String timers, String secondaryTimers) { @@ -553,9 +568,9 @@ public class NetworkTypeController extends StateMachine { quit(); break; case EVENT_INITIALIZE: - // The reason that we do it here is because some of the works below requires - // other modules (e.g. DataNetworkController, ServiceStateTracker), which is not - // created yet when NetworkTypeController is created. + // The reason that we do it here is that the work below requires other modules + // (e.g. DataNetworkController, ServiceStateTracker), which are not created + // when NetworkTypeController is created. registerForAllEvents(); parseCarrierConfigs(); break; @@ -579,6 +594,9 @@ public class NetworkTypeController extends StateMachine { log("Reset timers since physical channel config indications are off."); } resetAllTimers(); + mRatchetedNrBands.clear(); + mRatchetedNrBandwidths = 0; + mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN; } transitionToCurrentState(); break; @@ -609,10 +627,8 @@ public class NetworkTypeController extends StateMachine { resetAllTimers(); transitionToCurrentState(); break; - case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: - mPhysicalChannelConfigs = - mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); - if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); + case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED: + updatePhysicalChannelConfigs(); if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); } @@ -689,10 +705,8 @@ public class NetworkTypeController extends StateMachine { } mIsNrRestricted = isNrRestricted(); break; - case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: - mPhysicalChannelConfigs = - mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); - if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); + case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED: + updatePhysicalChannelConfigs(); if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) { @@ -770,10 +784,8 @@ public class NetworkTypeController extends StateMachine { } } break; - case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: - mPhysicalChannelConfigs = - mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); - if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); + case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED: + updatePhysicalChannelConfigs(); if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); if (isPhysicalLinkActive()) { @@ -854,10 +866,8 @@ public class NetworkTypeController extends StateMachine { } } break; - case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: - mPhysicalChannelConfigs = - mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); - if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); + case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED: + updatePhysicalChannelConfigs(); if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); if (!isPhysicalLinkActive()) { @@ -935,10 +945,8 @@ public class NetworkTypeController extends StateMachine { transitionWithTimerTo(mLegacyState); } break; - case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: - mPhysicalChannelConfigs = - mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); - if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); + case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED: + updatePhysicalChannelConfigs(); if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); } @@ -1016,10 +1024,8 @@ public class NetworkTypeController extends StateMachine { transitionWithTimerTo(mLegacyState); } break; - case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED: - mPhysicalChannelConfigs = - mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); - if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs); + case EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED: + updatePhysicalChannelConfigs(); if (isUsingPhysicalChannelConfigForRrcDetection()) { mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig(); } @@ -1050,6 +1056,77 @@ public class NetworkTypeController extends StateMachine { private final NrConnectedAdvancedState mNrConnectedAdvancedState = new NrConnectedAdvancedState(); + private void updatePhysicalChannelConfigs() { + List<PhysicalChannelConfig> physicalChannelConfigs = + mPhone.getServiceStateTracker().getPhysicalChannelConfigList(); + boolean isPccListEmpty = physicalChannelConfigs == null || physicalChannelConfigs.isEmpty(); + if (isPccListEmpty && isUsingPhysicalChannelConfigForRrcDetection()) { + log("Physical channel configs updated: not updating PCC fields for empty PCC list " + + "indicating RRC idle."); + mPhysicalChannelConfigs = physicalChannelConfigs; + return; + } + + int anchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN; + int anchorLteCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN; + int nrBandwidths = 0; + Set<Integer> nrBands = new HashSet<>(); + if (physicalChannelConfigs != null) { + for (PhysicalChannelConfig config : physicalChannelConfigs) { + if (config.getNetworkType() == TelephonyManager.NETWORK_TYPE_NR) { + if (config.getConnectionStatus() == CellInfo.CONNECTION_PRIMARY_SERVING + && anchorNrCellId == PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN) { + anchorNrCellId = config.getPhysicalCellId(); + } + nrBandwidths += config.getCellBandwidthDownlinkKhz(); + nrBands.add(config.getBand()); + } else if (config.getNetworkType() == TelephonyManager.NETWORK_TYPE_LTE) { + if (config.getConnectionStatus() == CellInfo.CONNECTION_PRIMARY_SERVING + && anchorLteCellId == PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN) { + anchorLteCellId = config.getPhysicalCellId(); + } + if (mIncludeLteForNrAdvancedThresholdBandwidth) { + nrBandwidths += config.getCellBandwidthDownlinkKhz(); + } + } + } + } + + // Update anchor NR cell from anchor LTE cell for NR NSA + if (anchorNrCellId == PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN + && anchorLteCellId != PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN) { + anchorNrCellId = anchorLteCellId; + } + + if (anchorNrCellId == PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN) { + if (!isPccListEmpty) { + log("Ignoring physical channel config fields without an anchor NR cell, " + + "either due to LTE-only configs or an unspecified cell ID."); + } + mRatchetedNrBandwidths = 0; + mRatchetedNrBands.clear(); + } else if (anchorNrCellId == mLastAnchorNrCellId && mRatchetPccFieldsForSameAnchorNrCell) { + log("Ratchet physical channel config fields since anchor NR cell is the same."); + mRatchetedNrBandwidths = Math.max(mRatchetedNrBandwidths, nrBandwidths); + mRatchetedNrBands.addAll(nrBands); + } else { + if (mRatchetPccFieldsForSameAnchorNrCell) { + log("Not ratcheting physical channel config fields since anchor NR cell changed: " + + mLastAnchorNrCellId + " -> " + anchorNrCellId); + } + mRatchetedNrBandwidths = nrBandwidths; + mRatchetedNrBands = nrBands; + } + + mLastAnchorNrCellId = anchorNrCellId; + mPhysicalChannelConfigs = physicalChannelConfigs; + if (DBG) { + log("Physical channel configs updated: anchorNrCell=" + mLastAnchorNrCellId + + ", nrBandwidths=" + mRatchetedNrBandwidths + ", nrBands=" + mRatchetedNrBands + + ", configs=" + mPhysicalChannelConfigs); + } + } + private void transitionWithTimerTo(IState destState) { String destName = destState.getName(); if (DBG) log("Transition with primary timer from " + mPreviousState + " to " + destName); @@ -1275,29 +1352,25 @@ public class NetworkTypeController extends StateMachine { // Check PCO requirement. For carriers using PCO to indicate whether the data connection is // NR advanced capable, mNrAdvancedCapablePcoId should be configured to non-zero. if (mNrAdvancedCapablePcoId > 0 && !mIsNrAdvancedAllowedByPco) { + if (DBG) log("isNrAdvanced: not allowed by PCO for PCO ID " + mNrAdvancedCapablePcoId); return false; } // Check if NR advanced is enabled when the device is roaming. Some carriers disable it // while the device is roaming. if (mServiceState.getDataRoaming() && !mEnableNrAdvancedWhileRoaming) { + if (DBG) log("isNrAdvanced: false because NR advanced is unavailable while roaming."); return false; } - int bandwidths = 0; - if (mPhone.getServiceStateTracker().getPhysicalChannelConfigList() != null) { - bandwidths = mPhone.getServiceStateTracker().getPhysicalChannelConfigList() - .stream() - .filter(config -> mIncludeLteForNrAdvancedThresholdBandwidth - || config.getNetworkType() == TelephonyManager.NETWORK_TYPE_NR) - .map(PhysicalChannelConfig::getCellBandwidthDownlinkKhz) - .mapToInt(Integer::intValue) - .sum(); - } - // Check if meeting minimum bandwidth requirement. For most carriers, there is no minimum // bandwidth requirement and mNrAdvancedThresholdBandwidth is 0. - if (mNrAdvancedThresholdBandwidth > 0 && bandwidths < mNrAdvancedThresholdBandwidth) { + if (mNrAdvancedThresholdBandwidth > 0 + && mRatchetedNrBandwidths < mNrAdvancedThresholdBandwidth) { + if (DBG) { + log("isNrAdvanced: false because bandwidths=" + mRatchetedNrBandwidths + + " does not meet the threshold=" + mNrAdvancedThresholdBandwidth); + } return false; } @@ -1311,17 +1384,17 @@ public class NetworkTypeController extends StateMachine { } private boolean isAdditionalNrAdvancedBand() { - if (ArrayUtils.isEmpty(mAdditionalNrAdvancedBandsList) - || mPhysicalChannelConfigs == null) { - return false; - } - for (PhysicalChannelConfig item : mPhysicalChannelConfigs) { - if (item.getNetworkType() == TelephonyManager.NETWORK_TYPE_NR - && ArrayUtils.contains(mAdditionalNrAdvancedBandsList, item.getBand())) { - return true; + if (mAdditionalNrAdvancedBands.isEmpty() || mRatchetedNrBands.isEmpty()) { + if (DBG && !mAdditionalNrAdvancedBands.isEmpty()) { + // Only log if mAdditionalNrAdvancedBands is empty to prevent log spam + log("isAdditionalNrAdvancedBand: false because bands are empty; configs=" + + mAdditionalNrAdvancedBands + ", bands=" + mRatchetedNrBands); } + return false; } - return false; + Set<Integer> intersection = new HashSet<>(mAdditionalNrAdvancedBands); + intersection.retainAll(mRatchetedNrBands); + return !intersection.isEmpty(); } private boolean isLte(int rat) { @@ -1388,8 +1461,13 @@ public class NetworkTypeController extends StateMachine { + mIsTimerResetEnabledForLegacyStateRrcIdle); pw.println("mLtePlusThresholdBandwidth=" + mLtePlusThresholdBandwidth); pw.println("mNrAdvancedThresholdBandwidth=" + mNrAdvancedThresholdBandwidth); - pw.println("mAdditionalNrAdvancedBandsList=" - + Arrays.toString(mAdditionalNrAdvancedBandsList)); + pw.println("mIncludeLteForNrAdvancedThresholdBandwidth=" + + mIncludeLteForNrAdvancedThresholdBandwidth); + pw.println("mRatchetPccFieldsForSameAnchorNrCell=" + mRatchetPccFieldsForSameAnchorNrCell); + pw.println("mRatchetedNrBandwidths=" + mRatchetedNrBandwidths); + pw.println("mAdditionalNrAdvancedBandsList=" + mAdditionalNrAdvancedBands); + pw.println("mRatchetedNrBands=" + mRatchetedNrBands); + pw.println("mLastAnchorNrCellId=" + mLastAnchorNrCellId); pw.println("mPrimaryTimerState=" + mPrimaryTimerState); pw.println("mSecondaryTimerState=" + mSecondaryTimerState); pw.println("mPreviousState=" + mPreviousState); diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java index 4e62d20eb2..0f4f528b72 100644 --- a/src/java/com/android/internal/telephony/Phone.java +++ b/src/java/com/android/internal/telephony/Phone.java @@ -715,6 +715,28 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { } /** + * Determines if the carrier prefers to use an in service sim for a normal routed emergency + * call. + * @return true when carrier config + * {@link CarrierConfigManager#KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL} + * is true. + */ + public boolean shouldPreferInServiceSimForNormalRoutedEmergencyCall() { + CarrierConfigManager configManager = (CarrierConfigManager) + getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); + PersistableBundle b = configManager.getConfigForSubId(getSubId(), CarrierConfigManager + .KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL); + if (b != null) { + return b.getBoolean(CarrierConfigManager + .KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL, + false); + } else { + // Default value set in CarrierConfigManager + return false; + } + } + + /** * When overridden the derived class needs to call * super.handleMessage(msg) so this method has a * a chance to process the message. @@ -2264,7 +2286,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { * * @return Current signal strength as SignalStrength */ - public SignalStrength getSignalStrength() { + public @NonNull SignalStrength getSignalStrength() { SignalStrengthController ssc = getSignalStrengthController(); if (ssc == null) { return new SignalStrength(); diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java index 8b9582419e..06ab58448a 100644 --- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java +++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java @@ -28,10 +28,12 @@ import android.os.Message; import android.os.PowerManager; import android.os.RegistrantList; import android.os.SystemProperties; +import android.provider.DeviceConfig; import android.sysprop.TelephonyProperties; import android.telephony.PhoneCapability; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -46,18 +48,21 @@ import java.util.NoSuchElementException; * This class manages phone's configuration which defines the potential capability (static) of the * phone and its current activated capability (current). * It gets and monitors static and current phone capability from the modem; send broadcast - * if they change, and and sends commands to modem to enable or disable phones. + * if they change, and sends commands to modem to enable or disable phones. */ public class PhoneConfigurationManager { public static final String DSDA = "dsda"; public static final String DSDS = "dsds"; public static final String TSTS = "tsts"; public static final String SSSS = ""; + /** DeviceConfig key for whether Virtual DSDA is enabled. */ + private static final String KEY_ENABLE_VIRTUAL_DSDA = "enable_virtual_dsda"; private static final String LOG_TAG = "PhoneCfgMgr"; private static final int EVENT_SWITCH_DSDS_CONFIG_DONE = 100; private static final int EVENT_GET_MODEM_STATUS = 101; private static final int EVENT_GET_MODEM_STATUS_DONE = 102; private static final int EVENT_GET_PHONE_CAPABILITY_DONE = 103; + private static final int EVENT_DEVICE_CONFIG_CHANGED = 104; private static PhoneConfigurationManager sInstance = null; private final Context mContext; @@ -70,6 +75,11 @@ public class PhoneConfigurationManager { private final Map<Integer, Boolean> mPhoneStatusMap; private MockableInterface mMi = new MockableInterface(); private TelephonyManager mTelephonyManager; + /** + * True if 'Virtual DSDA' i.e., in-call IMS connectivity on both subs with only single logical + * modem, is enabled. + */ + private boolean mVirtualDsdaEnabled; private static final RegistrantList sMultiSimConfigChangeRegistrants = new RegistrantList(); private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem"; private static final String BOOT_ALLOW_MOCK_MODEM_PROPERTY = "ro.boot.radio.allow_mock_modem"; @@ -102,6 +112,16 @@ public class PhoneConfigurationManager { mRadioConfig = RadioConfig.getInstance(); mHandler = new ConfigManagerHandler(); mPhoneStatusMap = new HashMap<>(); + mVirtualDsdaEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_VIRTUAL_DSDA, false); + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_TELEPHONY, Runnable::run, + properties -> { + if (TextUtils.equals(DeviceConfig.NAMESPACE_TELEPHONY, + properties.getNamespace())) { + mHandler.sendEmptyMessage(EVENT_DEVICE_CONFIG_CHANGED); + } + }); notifyCapabilityChanged(); @@ -127,10 +147,7 @@ public class PhoneConfigurationManager { // If virtual DSDA is enabled for this UE, then updates maxActiveVoiceSubscriptions to 2. private PhoneCapability maybeUpdateMaxActiveVoiceSubscriptions( final PhoneCapability staticCapability) { - boolean enableVirtualDsda = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enable_virtual_dsda); - - if (staticCapability.getLogicalModemList().size() > 1 && enableVirtualDsda) { + if (staticCapability.getLogicalModemList().size() > 1 && mVirtualDsdaEnabled) { return new PhoneCapability.Builder(staticCapability) .setMaxActiveVoiceSubscriptions(2) .build(); @@ -197,13 +214,22 @@ public class PhoneConfigurationManager { ar = (AsyncResult) msg.obj; if (ar != null && ar.exception == null) { mStaticCapability = (PhoneCapability) ar.result; - mStaticCapability = - maybeUpdateMaxActiveVoiceSubscriptions(mStaticCapability); notifyCapabilityChanged(); } else { log(msg.what + " failure. Not getting phone capability." + ar.exception); } break; + case EVENT_DEVICE_CONFIG_CHANGED: + boolean isVirtualDsdaEnabled = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_VIRTUAL_DSDA, false); + if (isVirtualDsdaEnabled != mVirtualDsdaEnabled) { + log("EVENT_DEVICE_CONFIG_CHANGED: from " + mVirtualDsdaEnabled + " to " + + isVirtualDsdaEnabled); + mVirtualDsdaEnabled = isVirtualDsdaEnabled; + } + break; + default: + log("Unknown event: " + msg.what); } } } @@ -317,6 +343,7 @@ public class PhoneConfigurationManager { mHandler, EVENT_GET_PHONE_CAPABILITY_DONE); mRadioConfig.getPhoneCapability(callback); } + mStaticCapability = maybeUpdateMaxActiveVoiceSubscriptions(mStaticCapability); log("getStaticPhoneCapability: mStaticCapability " + mStaticCapability); return mStaticCapability; } diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java index 9db186fbf8..ae8d033939 100644 --- a/src/java/com/android/internal/telephony/RILUtils.java +++ b/src/java/com/android/internal/telephony/RILUtils.java @@ -288,6 +288,7 @@ import static com.android.internal.telephony.RILConstants.RIL_UNSOL_VOICE_RADIO_ import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.radio.data.SliceInfo; import android.net.InetAddresses; import android.net.LinkAddress; import android.net.LinkProperties; @@ -989,8 +990,9 @@ public class RILUtils { * @param dp Data profile * @return The converted DataProfileInfo */ - public static android.hardware.radio.data.DataProfileInfo convertToHalDataProfile( + public static android.hardware.radio.data.DataProfileInfo convertToHalDataProfile(@Nullable DataProfile dp) { + if (dp == null) return null; android.hardware.radio.data.DataProfileInfo dpi = new android.hardware.radio.data.DataProfileInfo(); @@ -3722,7 +3724,8 @@ public class RILUtils { private static NetworkSliceInfo convertHalSliceInfo(android.hardware.radio.V1_6.SliceInfo si) { NetworkSliceInfo.Builder builder = new NetworkSliceInfo.Builder() .setSliceServiceType(si.sst) - .setMappedHplmnSliceServiceType(si.mappedHplmnSst); + .setMappedHplmnSliceServiceType(si.mappedHplmnSst) + .setStatus(convertHalSliceStatus(si.status)); if (si.sliceDifferentiator != NetworkSliceInfo.SLICE_DIFFERENTIATOR_NO_SLICE) { builder.setSliceDifferentiator(si.sliceDifferentiator) .setMappedHplmnSliceDifferentiator(si.mappedHplmnSD); @@ -3733,7 +3736,8 @@ public class RILUtils { private static NetworkSliceInfo convertHalSliceInfo(android.hardware.radio.data.SliceInfo si) { NetworkSliceInfo.Builder builder = new NetworkSliceInfo.Builder() .setSliceServiceType(si.sliceServiceType) - .setMappedHplmnSliceServiceType(si.mappedHplmnSst); + .setMappedHplmnSliceServiceType(si.mappedHplmnSst) + .setStatus(convertHalSliceStatus(si.status)); if (si.sliceDifferentiator != NetworkSliceInfo.SLICE_DIFFERENTIATOR_NO_SLICE) { builder.setSliceDifferentiator(si.sliceDifferentiator) .setMappedHplmnSliceDifferentiator(si.mappedHplmnSd); @@ -3741,6 +3745,23 @@ public class RILUtils { return builder.build(); } + @NetworkSliceInfo.SliceStatus private static int convertHalSliceStatus(byte status) { + switch (status) { + case SliceInfo.STATUS_CONFIGURED: + return NetworkSliceInfo.SLICE_STATUS_CONFIGURED; + case SliceInfo.STATUS_ALLOWED: + return NetworkSliceInfo.SLICE_STATUS_ALLOWED; + case SliceInfo.STATUS_REJECTED_NOT_AVAILABLE_IN_PLMN: + return NetworkSliceInfo.SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_PLMN; + case SliceInfo.STATUS_REJECTED_NOT_AVAILABLE_IN_REG_AREA: + return NetworkSliceInfo.SLICE_STATUS_REJECTED_NOT_AVAILABLE_IN_REGISTERED_AREA; + case SliceInfo.STATUS_DEFAULT_CONFIGURED: + return NetworkSliceInfo.SLICE_STATUS_DEFAULT_CONFIGURED; + default: + return NetworkSliceInfo.SLICE_STATUS_UNKNOWN; + } + } + private static TrafficDescriptor convertHalTrafficDescriptor( android.hardware.radio.V1_6.TrafficDescriptor td) throws IllegalArgumentException { String dnn = td.dnn.getDiscriminator() diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java index 50eea7f695..8a4967085f 100644 --- a/src/java/com/android/internal/telephony/ServiceStateTracker.java +++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java @@ -97,6 +97,7 @@ import com.android.internal.telephony.data.DataNetworkController.DataNetworkCont import com.android.internal.telephony.imsphone.ImsPhone; import com.android.internal.telephony.metrics.ServiceStateStats; import com.android.internal.telephony.metrics.TelephonyMetrics; +import com.android.internal.telephony.satellite.NtnCapabilityResolver; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState; @@ -404,6 +405,11 @@ public class ServiceStateTracker extends Handler { // state in case our service state was never broadcasted (we don't notify // service states when the subId is invalid) mPhone.notifyServiceStateChanged(mPhone.getServiceState()); + // On SubscriptionId changed from invalid to valid sub id, create + // ServiceStateProvider with valid sub id entry. Note: PollStateDone can update + // the DB again,for the SubID with any change detected at poll state request + log("Update SS information on moving from invalid to valid sub id"); + updateServiceStateToDb(mPhone.getServiceState()); } boolean restoreSelection = !context.getResources().getBoolean( @@ -2932,8 +2938,8 @@ public class ServiceStateTracker extends Handler { // Force display no service final boolean forceDisplayNoService = shouldForceDisplayNoService() && !mIsSimReady; - if (!forceDisplayNoService && Phone.isEmergencyCallOnly()) { - // No service but emergency call allowed + if (!forceDisplayNoService && (mEmergencyOnly || Phone.isEmergencyCallOnly())) { + // The slot is emc only or the slot is masked as oos due to device is emc only plmn = Resources.getSystem() .getText(com.android.internal.R.string.emergency_calls_only).toString(); } else { @@ -3419,6 +3425,7 @@ public class ServiceStateTracker extends Handler { updateNrFrequencyRangeFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS); updateNrStateFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS); + updateNtnCapability(); if (TelephonyUtils.IS_DEBUGGABLE && mPhone.getTelephonyTester() != null) { mPhone.getTelephonyTester().overrideServiceState(mNewSS); @@ -3759,10 +3766,7 @@ public class ServiceStateTracker extends Handler { mPhone.notifyServiceStateChanged(mPhone.getServiceState()); } - // insert into ServiceStateProvider. This will trigger apps to wake through JobScheduler - mPhone.getContext().getContentResolver() - .insert(getUriForSubscriptionId(mPhone.getSubId()), - getContentValuesForServiceState(mSS)); + updateServiceStateToDb(mPhone.getServiceState()); TelephonyMetrics.getInstance().writeServiceStateChanged(mPhone.getPhoneId(), mSS); mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS); @@ -3892,6 +3896,16 @@ public class ServiceStateTracker extends Handler { } } + /** + * Insert SS information into ServiceStateProvider DB table for a sub id. + * This will trigger apps to wake through JobScheduler + */ + private void updateServiceStateToDb(ServiceState serviceState) { + mPhone.getContext().getContentResolver() + .insert(getUriForSubscriptionId(mPhone.getSubId()), + getContentValuesForServiceState(serviceState)); + } + private String getOperatorNameFromEri() { String eriText = null; if (mPhone.isPhoneTypeCdma()) { @@ -5538,6 +5552,17 @@ public class ServiceStateTracker extends Handler { } } + private void updateNtnCapability() { + for (NetworkRegistrationInfo nri : mNewSS.getNetworkRegistrationInfoListForTransportType( + AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) { + NtnCapabilityResolver.resolveNtnCapability(nri, mSubId); + if (nri.isNonTerrestrialNetwork()) { + // Replace the existing NRI with the updated NRI. + mNewSS.addNetworkRegistrationInfo(nri); + } + } + } + /** * Check if device is non-roaming and always on home network. * diff --git a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java index 0ae1b5ce79..4b5eebc6e2 100644 --- a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java +++ b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java @@ -452,10 +452,12 @@ public class CarrierDisplayNameResolver { boolean forceDisplayNoService = mPhone.getServiceStateTracker().shouldForceDisplayNoService() && !isSimReady; ServiceState ss = getServiceState(); + // The slot is emc only or oos but the device is emc only. + boolean isEmcOnly = ss.isEmergencyOnly() || Phone.isEmergencyCallOnly(); if (ss.getState() == ServiceState.STATE_POWER_OFF && !forceDisplayNoService - && !Phone.isEmergencyCallOnly()) { + && !isEmcOnly) { plmn = null; - } else if (forceDisplayNoService || !Phone.isEmergencyCallOnly()) { + } else if (forceDisplayNoService || !isEmcOnly) { plmn = mContext.getResources().getString( com.android.internal.R.string.lockscreen_carrier_default); } else { diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java new file mode 100644 index 0000000000..87591deebc --- /dev/null +++ b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java @@ -0,0 +1,702 @@ +/* + * 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.data; + +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.telephony.SubscriptionManager.DEFAULT_PHONE_INDEX; +import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.net.NetworkCapabilities; +import android.os.AsyncResult; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.provider.Settings; +import android.telephony.AccessNetworkConstants; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.SignalStrength; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyDisplayInfo; +import android.util.IndentingPrintWriter; +import android.util.LocalLog; + +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.subscription.SubscriptionInfoInternal; +import com.android.internal.telephony.subscription.SubscriptionManagerService; +import com.android.internal.telephony.util.NotificationChannelController; +import com.android.telephony.Rlog; + + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; + +/** + * Recommend a data phone to use based on its availability. + */ +public class AutoDataSwitchController extends Handler { + /** Registration state changed. */ + public static final int EVALUATION_REASON_REGISTRATION_STATE_CHANGED = 1; + /** Telephony Display Info changed. */ + public static final int EVALUATION_REASON_DISPLAY_INFO_CHANGED = 2; + /** Signal Strength changed. */ + public static final int EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED = 3; + /** Default network capabilities changed or lost. */ + public static final int EVALUATION_REASON_DEFAULT_NETWORK_CHANGED = 4; + /** Data enabled settings changed. */ + public static final int EVALUATION_REASON_DATA_SETTINGS_CHANGED = 5; + /** Retry due to previous validation failed. */ + public static final int EVALUATION_REASON_RETRY_VALIDATION = 6; + /** Sim loaded which means slot mapping became available. */ + public static final int EVALUATION_REASON_SIM_LOADED = 7; + /** Voice call ended. */ + public static final int EVALUATION_REASON_VOICE_CALL_END = 8; + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "EVALUATION_REASON_", + value = {EVALUATION_REASON_REGISTRATION_STATE_CHANGED, + EVALUATION_REASON_DISPLAY_INFO_CHANGED, + EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED, + EVALUATION_REASON_DEFAULT_NETWORK_CHANGED, + EVALUATION_REASON_DATA_SETTINGS_CHANGED, + EVALUATION_REASON_RETRY_VALIDATION, + EVALUATION_REASON_SIM_LOADED, + EVALUATION_REASON_VOICE_CALL_END}) + public @interface AutoDataSwitchEvaluationReason {} + + private static final String LOG_TAG = "ADSC"; + + /** Event for service state changed. */ + private static final int EVENT_SERVICE_STATE_CHANGED = 1; + /** Event for display info changed. This is for getting 5G NSA or mmwave information. */ + private static final int EVENT_DISPLAY_INFO_CHANGED = 2; + /** Event for evaluate auto data switch opportunity. */ + private static final int EVENT_EVALUATE_AUTO_SWITCH = 3; + /** Event for signal strength changed. */ + private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4; + /** Event indicates the switch state is stable, proceed to validation as the next step. */ + private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE = 5; + + /** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */ + private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; + /** + * When starting this activity, this extra can also be specified to supply a Bundle of arguments + * to pass to that fragment when it is instantiated during the initial creation of the activity. + */ + private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS = + ":settings:show_fragment_args"; + /** The resource ID of the auto data switch fragment in settings. **/ + private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch"; + /** Notification tag **/ + private static final String AUTO_DATA_SWITCH_NOTIFICATION_TAG = "auto_data_switch"; + /** Notification ID **/ + private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1; + + private final @NonNull LocalLog mLocalLog = new LocalLog(128); + private final @NonNull Context mContext; + private final @NonNull SubscriptionManagerService mSubscriptionManagerService; + private final @NonNull PhoneSwitcher mPhoneSwitcher; + private final @NonNull AutoDataSwitchControllerCallback mPhoneSwitcherCallback; + private boolean mDefaultNetworkIsOnNonCellular = false; + /** {@code true} if we've displayed the notification the first time auto switch occurs **/ + private boolean mDisplayedNotification = false; + /** + * Time threshold in ms to define a internet connection status to be stable(e.g. out of service, + * in service, wifi is the default active network.etc), while -1 indicates auto switch + * feature disabled. + */ + private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1; + /** + * {@code true} if requires ping test before switching preferred data modem; otherwise, switch + * even if ping test fails. + */ + private boolean mRequirePingTestBeforeSwitch = true; + /** The count of consecutive auto switch validation failure **/ + private int mAutoSwitchValidationFailedCount = 0; + /** + * The maximum number of retries when a validation for switching failed. + */ + private int mAutoDataSwitchValidationMaxRetry; + + private @NonNull PhoneSignalStatus[] mPhonesSignalStatus; + + /** + * To track the signal status of a phone in order to evaluate whether it's a good candidate to + * switch to. + */ + private static class PhoneSignalStatus { + private @NonNull Phone mPhone; + private @NetworkRegistrationInfo.RegistrationState int mDataRegState = + NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; + private @NonNull TelephonyDisplayInfo mDisplayInfo; + private @NonNull SignalStrength mSignalStrength; + + private int mScore; + + private PhoneSignalStatus(@NonNull Phone phone) { + this.mPhone = phone; + this.mDisplayInfo = phone.getDisplayInfoController().getTelephonyDisplayInfo(); + this.mSignalStrength = phone.getSignalStrength(); + } + private int updateScore() { + // TODO: score = inservice? dcm.getscore() : 0 + return mScore; + } + @Override + public String toString() { + return "{phoneId=" + mPhone.getPhoneId() + + " score=" + mScore + " dataRegState=" + + NetworkRegistrationInfo.registrationStateToString(mDataRegState) + + " display=" + mDisplayInfo + " signalStrength=" + mSignalStrength.getLevel() + + "}"; + + } + } + + /** + * This is the callback used for listening events from {@link AutoDataSwitchController}. + */ + public abstract static class AutoDataSwitchControllerCallback { + /** + * Called when a target data phone is recommended by the controller. + * @param targetPhoneId The target phone Id. + * @param needValidation {@code true} if need a ping test to pass before switching. + */ + public abstract void onRequireValidation(int targetPhoneId, boolean needValidation); + + /** + * Called when a target data phone is demanded by the controller. + * @param targetPhoneId The target phone Id. + * @param reason The reason for the demand. + */ + public abstract void onRequireImmediatelySwitchToPhone(int targetPhoneId, + @AutoDataSwitchEvaluationReason int reason); + + /** + * Called when the controller asks to cancel any pending validation attempts because the + * environment is no longer suited for switching. + */ + public abstract void onRequireCancelAnyPendingAutoSwitchValidation(); + } + + /** + * @param context Context. + * @param looper Main looper. + * @param phoneSwitcher Phone switcher. + * @param phoneSwitcherCallback Callback for phone switcher to execute. + */ + public AutoDataSwitchController(@NonNull Context context, @NonNull Looper looper, + @NonNull PhoneSwitcher phoneSwitcher, + @NonNull AutoDataSwitchControllerCallback phoneSwitcherCallback) { + super(looper); + mContext = context; + mSubscriptionManagerService = SubscriptionManagerService.getInstance(); + mPhoneSwitcher = phoneSwitcher; + mPhoneSwitcherCallback = phoneSwitcherCallback; + readDeviceResourceConfig(); + int numActiveModems = PhoneFactory.getPhones().length; + mPhonesSignalStatus = new PhoneSignalStatus[numActiveModems]; + for (int phoneId = 0; phoneId < numActiveModems; phoneId++) { + registerAllEventsForPhone(phoneId); + } + } + + /** + * Called when active modem count changed, update all tracking events. + * @param numActiveModems The current number of active modems. + */ + public synchronized void onMultiSimConfigChanged(int numActiveModems) { + int oldActiveModems = mPhonesSignalStatus.length; + if (oldActiveModems == numActiveModems) return; + // Dual -> Single + for (int phoneId = numActiveModems; phoneId < oldActiveModems; phoneId++) { + Phone phone = mPhonesSignalStatus[phoneId].mPhone; + phone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this); + phone.getSignalStrengthController().unregisterForSignalStrengthChanged(this); + phone.getServiceStateTracker().unregisterForServiceStateChanged(this); + } + mPhonesSignalStatus = Arrays.copyOf(mPhonesSignalStatus, numActiveModems); + // Signal -> Dual + for (int phoneId = oldActiveModems; phoneId < numActiveModems; phoneId++) { + registerAllEventsForPhone(phoneId); + } + } + + /** + * Register all tracking events for a phone. + * @param phoneId The phone to register for all events. + */ + private void registerAllEventsForPhone(int phoneId) { + Phone phone = PhoneFactory.getPhone(phoneId); + if (phone != null) { + mPhonesSignalStatus[phoneId] = new PhoneSignalStatus(phone); + phone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged( + this, EVENT_DISPLAY_INFO_CHANGED, phoneId); + phone.getSignalStrengthController().registerForSignalStrengthChanged( + this, EVENT_SIGNAL_STRENGTH_CHANGED, phoneId); + phone.getServiceStateTracker().registerForServiceStateChanged(this, + EVENT_SERVICE_STATE_CHANGED, phoneId); + } else { + loge("Unexpected null phone " + phoneId + " when register all events"); + } + } + + /** + * Read the default device config from any default phone because the resource config are per + * device. No need to register callback for the same reason. + */ + private void readDeviceResourceConfig() { + Phone phone = PhoneFactory.getDefaultPhone(); + DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager(); + mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired(); + mAutoDataSwitchAvailabilityStabilityTimeThreshold = + dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold(); + mAutoDataSwitchValidationMaxRetry = + dataConfig.getAutoDataSwitchValidationMaxRetry(); + } + + @Override + public void handleMessage(@NonNull Message msg) { + AsyncResult ar; + int phoneId; + switch (msg.what) { + case EVENT_SERVICE_STATE_CHANGED: + ar = (AsyncResult) msg.obj; + phoneId = (int) ar.userObj; + onRegistrationStateChanged(phoneId); + break; + case EVENT_DISPLAY_INFO_CHANGED: + ar = (AsyncResult) msg.obj; + phoneId = (int) ar.userObj; + onDisplayInfoChanged(phoneId); + break; + case EVENT_EVALUATE_AUTO_SWITCH: + int reason = (int) msg.obj; + onEvaluateAutoDataSwitch(reason); + break; + case EVENT_MEETS_AUTO_DATA_SWITCH_STATE: + int targetPhoneId = msg.arg1; + boolean needValidation = (boolean) msg.obj; + log("require validation on phone " + targetPhoneId + + (needValidation ? "" : " no") + " need to pass"); + mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation); + break; + default: + loge("Unexpected event " + msg.what); + } + } + + /** + * Called when registration state changed. + */ + private void onRegistrationStateChanged(int phoneId) { + Phone phone = PhoneFactory.getPhone(phoneId); + if (phone != null) { + int oldRegState = mPhonesSignalStatus[phoneId].mDataRegState; + int newRegState = phone.getServiceState() + .getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN) + .getRegistrationState(); + if (newRegState != oldRegState) { + mPhonesSignalStatus[phoneId].mDataRegState = newRegState; + log("onRegistrationStateChanged: phone " + phoneId + " " + + NetworkRegistrationInfo.registrationStateToString(oldRegState) + + " -> " + + NetworkRegistrationInfo.registrationStateToString(newRegState)); + evaluateAutoDataSwitch(EVALUATION_REASON_REGISTRATION_STATE_CHANGED); + } else { + log("onRegistrationStateChanged: no change."); + } + } else { + loge("Unexpected null phone " + phoneId + " upon its registration state changed"); + } + } + + /** + * @return {@code true} if the phone state is considered in service. + */ + private boolean isInService(@NetworkRegistrationInfo.RegistrationState int dataRegState) { + return dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME + || dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING; + } + + /** + * Called when {@link TelephonyDisplayInfo} changed. This can happen when network types or + * override network types (5G NSA, 5G MMWAVE) change. + */ + private void onDisplayInfoChanged(int phoneId) { + Phone phone = PhoneFactory.getPhone(phoneId); + if (phone != null) { + TelephonyDisplayInfo displayInfo = phone.getDisplayInfoController() + .getTelephonyDisplayInfo(); + //TODO(b/260928808) + log("onDisplayInfoChanged:" + displayInfo); + } else { + loge("Unexpected null phone " + phoneId + " upon its display info changed"); + } + } + + /** + * Schedule for auto data switch evaluation. + * @param reason The reason for the evaluation. + */ + public void evaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) { + long delayMs = reason == EVALUATION_REASON_RETRY_VALIDATION + ? mAutoDataSwitchAvailabilityStabilityTimeThreshold + << mAutoSwitchValidationFailedCount + : 0; + if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) { + sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs); + } + } + + /** + * Evaluate for auto data switch opportunity. + * If suitable to switch, check that the suitable state is stable(or switch immediately if user + * turned off settings). + * @param reason The reason for the evaluation. + */ + private void onEvaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) { + // auto data switch feature is disabled. + if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return; + int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); + // check is valid DSDS + if (!isActiveSubId(defaultDataSubId) || mSubscriptionManagerService + .getActiveSubIdList(true).length <= 1) { + return; + } + Phone defaultDataPhone = PhoneFactory.getPhone(mSubscriptionManagerService.getPhoneId( + defaultDataSubId)); + if (defaultDataPhone == null) { + loge("onEvaluateAutoDataSwitch: cannot find the phone associated with default data" + + " subscription " + defaultDataSubId); + return; + } + int defaultDataPhoneId = defaultDataPhone.getPhoneId(); + int preferredPhoneId = mPhoneSwitcher.getPreferredDataPhoneId(); + log("onEvaluateAutoDataSwitch: defaultPhoneId: " + defaultDataPhoneId + + " preferredPhoneId: " + preferredPhoneId + + " reason: " + evaluationReasonToString(reason)); + if (preferredPhoneId == defaultDataPhoneId) { + // on default data sub + int candidatePhoneId = getSwitchCandidatePhoneId(defaultDataPhoneId); + if (candidatePhoneId != INVALID_PHONE_INDEX) { + startStabilityCheck(candidatePhoneId, mRequirePingTestBeforeSwitch); + } else { + cancelAnyPendingSwitch(); + } + } else { + // on backup data sub + Phone backupDataPhone = PhoneFactory.getPhone(preferredPhoneId); + if (backupDataPhone == null) { + loge("onEvaluateAutoDataSwitch: Unexpected null phone " + preferredPhoneId + + " as the current active data phone"); + return; + } + + if (!defaultDataPhone.isUserDataEnabled() || !backupDataPhone.isDataAllowed()) { + // immediately switch back if user disabled setting changes + mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX, + EVALUATION_REASON_DATA_SETTINGS_CHANGED); + return; + } + + if (mDefaultNetworkIsOnNonCellular) { + log("onEvaluateAutoDataSwitch: Default network is active on nonCellular transport"); + startStabilityCheck(DEFAULT_PHONE_INDEX, false); + return; + } + + if (mPhonesSignalStatus[preferredPhoneId].mDataRegState + != NetworkRegistrationInfo.REGISTRATION_STATE_HOME) { + // backup phone lost its HOME registration + startStabilityCheck(DEFAULT_PHONE_INDEX, false); + return; + } + + if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) { + // default phone is back to service + startStabilityCheck(DEFAULT_PHONE_INDEX, mRequirePingTestBeforeSwitch); + return; + } + + // cancel any previous attempts of switching back to default phone + cancelAnyPendingSwitch(); + } + } + + /** + * Called when consider switching from primary default data sub to another data sub. + * @return the target subId if a suitable candidate is found, otherwise return + * {@link SubscriptionManager#INVALID_PHONE_INDEX} + */ + private int getSwitchCandidatePhoneId(int defaultPhoneId) { + Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId); + if (defaultDataPhone == null) { + log("getSwitchCandidatePhoneId: no sim loaded"); + return INVALID_PHONE_INDEX; + } + + if (!defaultDataPhone.isUserDataEnabled()) { + log("getSwitchCandidatePhoneId: user disabled data"); + return INVALID_PHONE_INDEX; + } + + if (mDefaultNetworkIsOnNonCellular) { + // Exists other active default transport + log("getSwitchCandidatePhoneId: Default network is active on non-cellular transport"); + return INVALID_PHONE_INDEX; + } + + // check whether primary and secondary signal status are worth switching + if (isInService(mPhonesSignalStatus[defaultPhoneId].mDataRegState)) { + log("getSwitchCandidatePhoneId: DDS is in service"); + return INVALID_PHONE_INDEX; + } + for (int phoneId = 0; phoneId < mPhonesSignalStatus.length; phoneId++) { + if (phoneId != defaultPhoneId) { + // the alternative phone must have HOME availability + if (mPhonesSignalStatus[phoneId].mDataRegState + == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) { + log("getSwitchCandidatePhoneId: found phone " + phoneId + + " in HOME service"); + Phone secondaryDataPhone = PhoneFactory.getPhone(phoneId); + if (secondaryDataPhone != null && // check auto switch feature enabled + secondaryDataPhone.isDataAllowed()) { + return phoneId; + } + } + } + } + return INVALID_PHONE_INDEX; + } + + /** + * Called when the current environment suits auto data switch. + * Start pre-switch validation if the current environment suits auto data switch for + * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS. + * @param targetPhoneId the target phone Id. + * @param needValidation {@code true} if validation is needed. + */ + private void startStabilityCheck(int targetPhoneId, boolean needValidation) { + log("startAutoDataSwitchStabilityCheck: targetPhoneId=" + targetPhoneId + + " needValidation=" + needValidation); + if (!hasMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, needValidation)) { + sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetPhoneId, + 0/*placeholder*/, + needValidation), + mAutoDataSwitchAvailabilityStabilityTimeThreshold); + } + } + + /** Auto data switch evaluation reason to string. */ + public static @NonNull String evaluationReasonToString( + @AutoDataSwitchEvaluationReason int reason) { + switch (reason) { + case EVALUATION_REASON_REGISTRATION_STATE_CHANGED: return "REGISTRATION_STATE_CHANGED"; + case EVALUATION_REASON_DISPLAY_INFO_CHANGED: return "DISPLAY_INFO_CHANGED"; + case EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED: return "SIGNAL_STRENGTH_CHANGED"; + case EVALUATION_REASON_DEFAULT_NETWORK_CHANGED: return "DEFAULT_NETWORK_CHANGED"; + case EVALUATION_REASON_DATA_SETTINGS_CHANGED: return "DATA_SETTINGS_CHANGED"; + case EVALUATION_REASON_RETRY_VALIDATION: return "RETRY_VALIDATION"; + case EVALUATION_REASON_SIM_LOADED: return "SIM_LOADED"; + case EVALUATION_REASON_VOICE_CALL_END: return "VOICE_CALL_END"; + } + return "Unknown(" + reason + ")"; + } + + /** @return {@code true} if the sub is active. */ + private boolean isActiveSubId(int subId) { + SubscriptionInfoInternal subInfo = mSubscriptionManagerService + .getSubscriptionInfoInternal(subId); + return subInfo != null && subInfo.isActive(); + } + + /** + * Called when default network capabilities changed. If default network is active on + * non-cellular, switch back to the default data phone. If default network is lost, try to find + * another sub to switch to. + * @param networkCapabilities {@code null} indicates default network lost. + */ + public void updateDefaultNetworkCapabilities( + @Nullable NetworkCapabilities networkCapabilities) { + if (networkCapabilities != null) { + // Exists default network + mDefaultNetworkIsOnNonCellular = !networkCapabilities.hasTransport(TRANSPORT_CELLULAR); + if (mDefaultNetworkIsOnNonCellular + && isActiveSubId(mPhoneSwitcher.getAutoSelectedDataSubId())) { + log("default network is active on non cellular, switch back to default"); + evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED); + } + } else { + log("default network is lost, try to find another active sub to switch to"); + mDefaultNetworkIsOnNonCellular = false; + evaluateAutoDataSwitch(EVALUATION_REASON_DEFAULT_NETWORK_CHANGED); + } + } + + /** + * Cancel any auto switch attempts when the current environment is not suitable for auto switch. + */ + private void cancelAnyPendingSwitch() { + resetFailedCount(); + removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE); + mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation(); + } + + /** + * Display a notification the first time auto data switch occurs. + * @param phoneId The phone Id of the current preferred phone. + * @param isDueToAutoSwitch {@code true} if the switch was due to auto data switch feature. + */ + public void displayAutoDataSwitchNotification(int phoneId, boolean isDueToAutoSwitch) { + NotificationManager notificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + if (mDisplayedNotification) { + // cancel posted notification if any exist + log("displayAutoDataSwitchNotification: canceling any notifications for phone " + + phoneId); + notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG, + AUTO_DATA_SWITCH_NOTIFICATION_ID); + return; + } + // proceed only the first time auto data switch occurs, which includes data during call + if (!isDueToAutoSwitch) { + return; + } + SubscriptionInfo subInfo = mSubscriptionManagerService + .getSubscriptionInfo(mSubscriptionManagerService.getSubId(phoneId)); + if (subInfo == null || subInfo.isOpportunistic()) { + loge("displayAutoDataSwitchNotification: phoneId=" + + phoneId + " unexpected subInfo " + subInfo); + return; + } + int subId = subInfo.getSubscriptionId(); + logl("displayAutoDataSwitchNotification: display for subId=" + subId); + // "Mobile network settings" screen / dialog + Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS); + final Bundle fragmentArgs = new Bundle(); + // Special contract for Settings to highlight permission row + fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID); + intent.putExtra(Settings.EXTRA_SUB_ID, subId); + intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs); + PendingIntent contentIntent = PendingIntent.getActivity( + mContext, subId, intent, PendingIntent.FLAG_IMMUTABLE); + + CharSequence activeCarrierName = subInfo.getDisplayName(); + CharSequence contentTitle = mContext.getString( + com.android.internal.R.string.auto_data_switch_title, activeCarrierName); + CharSequence contentText = mContext.getText( + com.android.internal.R.string.auto_data_switch_content); + + final Notification notif = new Notification.Builder(mContext) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setSmallIcon(android.R.drawable.stat_sys_warning) + .setColor(mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color)) + .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS) + .setContentIntent(contentIntent) + .setStyle(new Notification.BigTextStyle().bigText(contentText)) + .build(); + notificationManager.notify(AUTO_DATA_SWITCH_NOTIFICATION_TAG, + AUTO_DATA_SWITCH_NOTIFICATION_ID, notif); + mDisplayedNotification = true; + } + + /** Enable future switch retry again. Called when switch condition changed. */ + public void resetFailedCount() { + mAutoSwitchValidationFailedCount = 0; + } + + /** + * Called when skipped switch due to validation failed. Schedule retry to switch again. + */ + public void evaluateRetryOnValidationFailed() { + if (mAutoSwitchValidationFailedCount < mAutoDataSwitchValidationMaxRetry) { + evaluateAutoDataSwitch(EVALUATION_REASON_RETRY_VALIDATION); + mAutoSwitchValidationFailedCount++; + } else { + logl("evaluateRetryOnValidationFailed: reached max auto switch retry count " + + mAutoDataSwitchValidationMaxRetry); + mAutoSwitchValidationFailedCount = 0; + } + } + + /** + * Log debug messages. + * @param s debug messages + */ + private void log(@NonNull String s) { + Rlog.d(LOG_TAG, s); + } + + /** + * Log error messages. + * @param s error messages + */ + private void loge(@NonNull String s) { + Rlog.e(LOG_TAG, s); + } + + /** + * Log debug messages and also log into the local log. + * @param s debug messages + */ + private void logl(@NonNull String s) { + log(s); + mLocalLog.log(s); + } + + /** + * Dump the state of DataNetworkController + * + * @param fd File descriptor + * @param printWriter Print writer + * @param args Arguments + */ + public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); + pw.println("AutoDataSwitchController:"); + pw.increaseIndent(); + pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry + + " mAutoSwitchValidationFailedCount=" + mAutoSwitchValidationFailedCount); + pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeSwitch); + pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold=" + + mAutoDataSwitchAvailabilityStabilityTimeThreshold); + pw.increaseIndent(); + for (PhoneSignalStatus status: mPhonesSignalStatus) { + pw.println(status); + } + pw.decreaseIndent(); + mLocalLog.dump(fd, pw, args); + pw.decreaseIndent(); + } +} diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java index 78450a81ed..f7fe4ad0f8 100644 --- a/src/java/com/android/internal/telephony/data/DataConfigManager.java +++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java @@ -32,6 +32,7 @@ import android.telephony.Annotation.NetCapability; import android.telephony.Annotation.NetworkType; import android.telephony.CarrierConfigManager; import android.telephony.ServiceState; +import android.telephony.SignalStrength; import android.telephony.SubscriptionManager; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; @@ -199,8 +200,6 @@ public class DataConfigManager extends Handler { "anomaly_setup_data_call_failure"; /** DeviceConfig key of anomaly report threshold for frequent network-unwanted call. */ private static final String KEY_ANOMALY_NETWORK_UNWANTED = "anomaly_network_unwanted"; - /** DeviceConfig key of anomaly report threshold for frequent change of preferred network. */ - private static final String KEY_ANOMALY_QNS_CHANGE_NETWORK = "anomaly_qns_change_network"; /** DeviceConfig key of anomaly report threshold for invalid QNS params. */ private static final String KEY_ANOMALY_QNS_PARAM = "anomaly_qns_param"; /** DeviceConfig key of anomaly report threshold for DataNetwork stuck in connecting state. */ @@ -214,13 +213,8 @@ public class DataConfigManager extends Handler { "anomaly_network_handover_timeout"; /** DeviceConfig key of anomaly report: True for enabling APN config invalidity detection */ private static final String KEY_ANOMALY_APN_CONFIG_ENABLED = "anomaly_apn_config_enabled"; - /** DeviceConfig key of the time threshold in ms for defining a network status to be stable. **/ - private static final String KEY_AUTO_DATA_SWITCH_AVAILABILITY_STABILITY_TIME_THRESHOLD = - "auto_data_switch_availability_stability_time_threshold"; - /** DeviceConfig key of the maximum number of retries when a validation for switching failed.**/ - private static final String KEY_AUTO_DATA_SWITCH_VALIDATION_MAX_RETRY = - "auto_data_switch_validation_max_retry"; - + /** Invalid auto data switch score. */ + private static final int INVALID_AUTO_DATA_SWITCH_SCORE = -1; /** Anomaly report thresholds for frequent setup data call failure. */ private EventFrequency mSetupDataCallAnomalyReportThreshold; @@ -301,6 +295,12 @@ public class DataConfigManager extends Handler { private @NonNull final List<HandoverRule> mHandoverRuleList = new ArrayList<>(); /** {@code True} keep IMS network in case of moving to non VOPS area; {@code false} otherwise.*/ private boolean mShouldKeepNetworkUpInNonVops = false; + /** + * A map of network types to the estimated downlink values by signal strength 0 - 4 for that + * network type + */ + private @NonNull final @DataConfigNetworkType Map<String, int[]> + mAutoDataSwitchNetworkTypeSignalMap = new ConcurrentHashMap<>(); /** * Constructor @@ -452,6 +452,7 @@ public class DataConfigManager extends Handler { updateBandwidths(); updateTcpBuffers(); updateHandoverRules(); + updateAutoDataSwitchConfig(); log("Carrier config updated. Config is " + (isConfigCarrierSpecific() ? "" : "not ") + "carrier specific."); @@ -918,6 +919,84 @@ public class DataConfigManager extends Handler { } /** + * Update the network type and signal strength score table for auto data switch decisions. + */ + private void updateAutoDataSwitchConfig() { + synchronized (this) { + mAutoDataSwitchNetworkTypeSignalMap.clear(); + final PersistableBundle table = mCarrierConfig.getPersistableBundle( + CarrierConfigManager.KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE); + String[] networkTypeKeys = { + DATA_CONFIG_NETWORK_TYPE_GPRS, + DATA_CONFIG_NETWORK_TYPE_EDGE, + DATA_CONFIG_NETWORK_TYPE_UMTS, + DATA_CONFIG_NETWORK_TYPE_CDMA, + DATA_CONFIG_NETWORK_TYPE_1xRTT, + DATA_CONFIG_NETWORK_TYPE_EVDO_0, + DATA_CONFIG_NETWORK_TYPE_EVDO_A, + DATA_CONFIG_NETWORK_TYPE_HSDPA, + DATA_CONFIG_NETWORK_TYPE_HSUPA, + DATA_CONFIG_NETWORK_TYPE_HSPA, + DATA_CONFIG_NETWORK_TYPE_EVDO_B, + DATA_CONFIG_NETWORK_TYPE_EHRPD, + DATA_CONFIG_NETWORK_TYPE_IDEN, + DATA_CONFIG_NETWORK_TYPE_LTE, + DATA_CONFIG_NETWORK_TYPE_HSPAP, + DATA_CONFIG_NETWORK_TYPE_GSM, + DATA_CONFIG_NETWORK_TYPE_TD_SCDMA, + DATA_CONFIG_NETWORK_TYPE_NR_NSA, + DATA_CONFIG_NETWORK_TYPE_NR_NSA_MMWAVE, + DATA_CONFIG_NETWORK_TYPE_NR_SA, + DATA_CONFIG_NETWORK_TYPE_NR_SA_MMWAVE + }; + if (table != null) { + for (String networkType : networkTypeKeys) { + int[] scores = table.getIntArray(networkType); + if (scores != null + && scores.length == SignalStrength.NUM_SIGNAL_STRENGTH_BINS) { + for (int i = 0; i < scores.length; i++) { + if (scores[i] < 0) { + loge("Auto switch score must not < 0 for network type " + + networkType); + break; + } + if (i == scores.length - 1) { + mAutoDataSwitchNetworkTypeSignalMap.put(networkType, scores); + } + } + } else { + loge("Auto switch score table should specify " + + SignalStrength.NUM_SIGNAL_STRENGTH_BINS + + " signal strength for network type " + networkType); + } + } + } + } + } + + /** + * @param displayInfo The displayed network info. + * @param signalStrength The signal strength. + * @return Score base on network type and signal strength to inform auto data switch decision. + */ + public int getAutoDataSwitchScore(@NonNull TelephonyDisplayInfo displayInfo, + @NonNull SignalStrength signalStrength) { + int[] scores = mAutoDataSwitchNetworkTypeSignalMap.get( + getDataConfigNetworkType(displayInfo)); + return scores != null ? scores[signalStrength.getLevel()] : INVALID_AUTO_DATA_SWITCH_SCORE; + } + + /** + * @return The tolerated gap of score for auto data switch decision, larger than which the + * device will switch to the SIM with higher score. If 0, the device always switch to the higher + * score SIM. If < 0, the network type and signal strength based auto switch is disabled. + */ + public int getAutoDataSwitchScoreTolerance() { + return mResources.getInteger(com.android.internal.R.integer + .auto_data_switch_score_tolerance); + } + + /** * @return The maximum number of retries when a validation for switching failed. */ public int getAutoDataSwitchValidationMaxRetry() { @@ -1264,6 +1343,15 @@ public class DataConfigManager extends Handler { } /** + * @return {@code true} if allow sending null data profile to ask modem to clear the initial + * attach data profile. + */ + public boolean allowClearInitialAttachDataProfile() { + return mResources.getBoolean( + com.android.internal.R.bool.allow_clear_initial_attach_data_profile); + } + + /** * Log debug messages. * @param s debug messages */ @@ -1315,9 +1403,15 @@ public class DataConfigManager extends Handler { pw.println("mNetworkDisconnectingTimeout=" + mNetworkDisconnectingTimeout); pw.println("mNetworkHandoverTimeout=" + mNetworkHandoverTimeout); pw.println("mIsApnConfigAnomalyReportEnabled=" + mIsApnConfigAnomalyReportEnabled); + pw.println("Auto data switch:"); + pw.increaseIndent(); + pw.println("getAutoDataSwitchScoreTolerance=" + getAutoDataSwitchScoreTolerance()); + mAutoDataSwitchNetworkTypeSignalMap.forEach((key, value) -> pw.println(key + ":" + + Arrays.toString(value))); pw.println("getAutoDataSwitchAvailabilityStabilityTimeThreshold=" + getAutoDataSwitchAvailabilityStabilityTimeThreshold()); pw.println("getAutoDataSwitchValidationMaxRetry=" + getAutoDataSwitchValidationMaxRetry()); + pw.decreaseIndent(); pw.println("Metered APN types=" + mMeteredApnTypes.stream() .map(ApnSetting::getApnTypeString).collect(Collectors.joining(","))); pw.println("Roaming metered APN types=" + mRoamingMeteredApnTypes.stream() @@ -1359,6 +1453,7 @@ public class DataConfigManager extends Handler { pw.println("isEnhancedIwlanHandoverCheckEnabled=" + isEnhancedIwlanHandoverCheckEnabled()); pw.println("isTetheringProfileDisabledForRoaming=" + isTetheringProfileDisabledForRoaming()); + pw.println("allowClearInitialAttachDataProfile=" + allowClearInitialAttachDataProfile()); pw.decreaseIndent(); } } diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java index d533933bf2..6ba251b430 100644 --- a/src/java/com/android/internal/telephony/data/DataNetwork.java +++ b/src/java/com/android/internal/telephony/data/DataNetwork.java @@ -664,6 +664,11 @@ public class DataNetwork extends StateMachine { */ private @NetworkType int mLastKnownDataNetworkType; + /** + * The last known roaming state of this data network. + */ + private boolean mLastKnownRoamingState; + /** The reason that why setting up this data network is allowed. */ private @NonNull DataAllowedReason mDataAllowedReason; @@ -923,6 +928,7 @@ public class DataNetwork extends StateMachine { } mTransport = transport; mLastKnownDataNetworkType = getDataNetworkType(); + mLastKnownRoamingState = mPhone.getServiceState().getDataRoamingFromRegistration(); mDataAllowedReason = dataAllowedReason; dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime()); mAttachedNetworkRequestList.addAll(networkRequestList); @@ -1136,6 +1142,15 @@ public class DataNetwork extends StateMachine { if (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN) { mLastKnownDataNetworkType = networkType; } + NetworkRegistrationInfo nri = + mPhone.getServiceState() + .getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + if (nri != null && nri.isInService()) { + mLastKnownRoamingState = nri.getNetworkRegistrationState() + == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING; + } updateSuspendState(); updateNetworkCapabilities(); break; @@ -2224,7 +2239,11 @@ public class DataNetwork extends StateMachine { TrafficDescriptor trafficDescriptor = mDataProfile.getTrafficDescriptor(); final boolean matchAllRuleAllowed = trafficDescriptor == null - || !TextUtils.isEmpty(trafficDescriptor.getDataNetworkName()); + || !TextUtils.isEmpty(trafficDescriptor.getDataNetworkName()) + // Both OsAppId and APN name are null. This helps for modem to handle when we + // are on 5G or LTE with URSP support in falling back to default network. + || (TextUtils.isEmpty(trafficDescriptor.getDataNetworkName()) + && trafficDescriptor.getOsAppId() == null); int accessNetwork = DataUtils.networkTypeToAccessNetworkType(dataNetworkType); @@ -3391,6 +3410,13 @@ public class DataNetwork extends StateMachine { } /** + * @return The last known roaming state of this data network. + */ + public boolean getLastKnownRoamingState() { + return mLastKnownRoamingState; + } + + /** * @return The PCO data received from the network. */ public @NonNull Map<Integer, PcoData> getPcoData() { diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java index 2ee1ffd3fb..f9e7510612 100644 --- a/src/java/com/android/internal/telephony/data/DataNetworkController.java +++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java @@ -1983,21 +1983,31 @@ public class DataNetworkController extends Handler { } int sourceAccessNetwork = DataUtils.networkTypeToAccessNetworkType( sourceNetworkType); - + NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + boolean isWwanInService = false; + if (nri != null && nri.isInService()) { + isWwanInService = true; + } + // If WWAN is inService, use the real roaming state reported by modem instead of + // using the overridden roaming state, otherwise get last known roaming state stored + // in data network. + boolean isRoaming = isWwanInService ? mServiceState.getDataRoamingFromRegistration() + : dataNetwork.getLastKnownRoamingState(); int targetAccessNetwork = DataUtils.networkTypeToAccessNetworkType( getDataNetworkType(DataUtils.getTargetTransport(dataNetwork.getTransport()))); NetworkCapabilities capabilities = dataNetwork.getNetworkCapabilities(); log("evaluateDataNetworkHandover: " + "source=" + AccessNetworkType.toString(sourceAccessNetwork) + ", target=" + AccessNetworkType.toString(targetAccessNetwork) + + ", roaming=" + isRoaming + ", ServiceState=" + mServiceState + ", capabilities=" + capabilities); // Matching the rules by the configured order. Bail out if find first matching rule. for (HandoverRule rule : handoverRules) { - // Check if the rule is only for roaming and we are not roaming. Use the real - // roaming state reported by modem instead of using the overridden roaming state. - if (rule.isOnlyForRoaming && !mServiceState.getDataRoamingFromRegistration()) { + // Check if the rule is only for roaming and we are not roaming. + if (rule.isOnlyForRoaming && !isRoaming) { // If the rule is for roaming only, and the device is not roaming, then bypass // this rule. continue; @@ -3378,7 +3388,8 @@ public class DataNetworkController extends Handler { if (oldPsNri == null || oldPsNri.getAccessNetworkTechnology() != newPsNri.getAccessNetworkTechnology() - || (!oldPsNri.isInService() && newPsNri.isInService())) { + || (!oldPsNri.isInService() && newPsNri.isInService()) + || (oldPsNri.isRoaming() && !newPsNri.isRoaming())) { return true; } diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java index 893ec4174b..0878ccf48a 100644 --- a/src/java/com/android/internal/telephony/data/DataProfileManager.java +++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java @@ -78,13 +78,6 @@ public class DataProfileManager extends Handler { private final String mLogTag; private final LocalLog mLocalLog = new LocalLog(128); - /** - * Should only be used by update updateDataProfiles() to indicate whether resend IA to modem - * regardless whether IA changed. - **/ - private final boolean FORCED_UPDATE_IA = true; - private final boolean ONLY_UPDATE_IA_IF_CHANGED = false; - /** Data network controller. */ private final @NonNull DataNetworkController mDataNetworkController; @@ -202,11 +195,12 @@ public class DataProfileManager extends Handler { switch (msg.what) { case EVENT_SIM_REFRESH: log("Update data profiles due to SIM refresh."); - updateDataProfiles(FORCED_UPDATE_IA); + updateDataProfiles(!mDataConfigManager.allowClearInitialAttachDataProfile() + /*force update IA*/); break; case EVENT_APN_DATABASE_CHANGED: log("Update data profiles due to APN db updated."); - updateDataProfiles(ONLY_UPDATE_IA_IF_CHANGED); + updateDataProfiles(false/*force update IA*/); break; default: loge("Unexpected event " + msg); @@ -219,9 +213,8 @@ public class DataProfileManager extends Handler { */ private void onCarrierConfigUpdated() { log("Update data profiles due to carrier config updated."); - updateDataProfiles(FORCED_UPDATE_IA); - - //TODO: more works needed to be done here. + updateDataProfiles(!mDataConfigManager.allowClearInitialAttachDataProfile() + /*force update IA*/); } /** @@ -258,7 +251,8 @@ public class DataProfileManager extends Handler { * Update all data profiles, including preferred data profile, and initial attach data profile. * Also send those profiles down to the modem if needed. * - * @param forceUpdateIa If {@code true}, we should always send IA again to modem. + * @param forceUpdateIa If {@code true}, we should always send initial attach data profile again + * to modem. */ private void updateDataProfiles(boolean forceUpdateIa) { List<DataProfile> profiles = new ArrayList<>(); @@ -444,7 +438,7 @@ public class DataProfileManager extends Handler { if (defaultProfile == null || defaultProfile.equals(mPreferredDataProfile)) return; // Save the preferred data profile into database. setPreferredDataProfile(defaultProfile); - updateDataProfiles(ONLY_UPDATE_IA_IF_CHANGED); + updateDataProfiles(false/*force update IA*/); } /** @@ -567,7 +561,8 @@ public class DataProfileManager extends Handler { * attach. In this case, exception can be configured through * {@link CarrierConfigManager#KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY}. * - * @param forceUpdateIa If {@code true}, we should always send IA again to modem. + * @param forceUpdateIa If {@code true}, we should always send initial attach data profile again + * to modem. */ private void updateInitialAttachDataProfileAtModem(boolean forceUpdateIa) { DataProfile initialAttachDataProfile = null; @@ -589,9 +584,8 @@ public class DataProfileManager extends Handler { mInitialAttachDataProfile = initialAttachDataProfile; logl("Initial attach data profile updated as " + mInitialAttachDataProfile + " or forceUpdateIa= " + forceUpdateIa); - // TODO: Push the null data profile to modem on new AIDL HAL. Modem should clear the IA - // APN, tracking for U b/227579876, now using forceUpdateIa which always push to modem - if (mInitialAttachDataProfile != null) { + if (mInitialAttachDataProfile != null || mDataConfigManager + .allowClearInitialAttachDataProfile()) { mWwanDataServiceManager.setInitialAttachApn(mInitialAttachDataProfile, mPhone.getServiceState().getDataRoamingFromRegistration(), null); } diff --git a/src/java/com/android/internal/telephony/data/DataRetryManager.java b/src/java/com/android/internal/telephony/data/DataRetryManager.java index b6ad101693..314668973b 100644 --- a/src/java/com/android/internal/telephony/data/DataRetryManager.java +++ b/src/java/com/android/internal/telephony/data/DataRetryManager.java @@ -1469,9 +1469,9 @@ public class DataRetryManager extends Handler { } else { Intent intent = new Intent(ACTION_RETRY); intent.putExtra(ACTION_RETRY_EXTRA_HASHCODE, dataRetryEntry.hashCode()); - // No need to wake up the device at the exact time, the retry can wait util next time - // the device wake up to save power. - mAlarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, + // No need to wake up the device, the retry can wait util next time the device wake up + // to save power. + mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, dataRetryEntry.retryElapsedTime, PendingIntent.getBroadcast(mPhone.getContext(), dataRetryEntry.hashCode() /*Unique identifier of this retry attempt*/, diff --git a/src/java/com/android/internal/telephony/data/DataServiceManager.java b/src/java/com/android/internal/telephony/data/DataServiceManager.java index 2733aff3ae..bb0e8c3cb2 100644 --- a/src/java/com/android/internal/telephony/data/DataServiceManager.java +++ b/src/java/com/android/internal/telephony/data/DataServiceManager.java @@ -809,11 +809,12 @@ public class DataServiceManager extends Handler { * Set an APN to initial attach network. * * @param dataProfile Data profile used for data network setup. See {@link DataProfile}. + * {@code null} to clear any previous data profiles. * @param isRoaming True if the device is data roaming. * @param onCompleteMessage The result message for this request. Null if the client does not * care about the result. */ - public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, + public void setInitialAttachApn(@Nullable DataProfile dataProfile, boolean isRoaming, Message onCompleteMessage) { if (DBG) log("setInitialAttachApn"); if (!mBound) { diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java index 5178ae4831..f8365bbef5 100644 --- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java +++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java @@ -130,6 +130,14 @@ public class DataSettingsManager extends Handler { } /** + * Called when user data enabled state changed. + * + * @param enabled {@code true} indicates user mobile data is enabled. + * @param callingPackage The package that changed the data enabled state. + */ + public void onUserDataEnabledChanged(boolean enabled, @NonNull String callingPackage) {} + + /** * Called when overall data enabled state changed. * * @param enabled {@code true} indicates mobile data is enabled. @@ -289,6 +297,23 @@ public class DataSettingsManager extends Handler { } } }, this::post); + // some overall mobile data override policy depend on whether DDS is user data enabled. + for (Phone phone : PhoneFactory.getPhones()) { + if (phone.getPhoneId() != mPhone.getPhoneId()) { + phone.getDataSettingsManager().registerCallback(new DataSettingsManagerCallback( + this::post) { + @Override + public void onUserDataEnabledChanged(boolean enabled, + @NonNull String callingPackage) { + log("phone" + phone.getPhoneId() + " onUserDataEnabledChanged " + + enabled + " by " + callingPackage + + ", reevaluating mobile data policies"); + DataSettingsManager.this.updateDataEnabledAndNotify( + TelephonyManager.DATA_ENABLED_REASON_OVERRIDE); + } + }); + } + } updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_UNKNOWN); } @@ -416,6 +441,8 @@ public class DataSettingsManager extends Handler { if (changed) { logl("UserDataEnabled changed to " + enabled); mPhone.notifyUserMobileDataStateChanged(enabled); + mDataSettingsManagerCallbacks.forEach(callback -> callback.invokeFromExecutor( + () -> callback.onUserDataEnabledChanged(enabled, callingPackage))); updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_USER, callingPackage); } } diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java index 6c6f06455d..375b2503a7 100644 --- a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java +++ b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java @@ -23,7 +23,10 @@ import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.content.Intent; +import android.database.ContentObserver; import android.net.NetworkAgent; +import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -34,6 +37,7 @@ import android.telephony.Annotation.ValidationStatus; import android.telephony.CellSignalStrength; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.IndentingPrintWriter; import android.util.LocalLog; @@ -127,12 +131,24 @@ public class DataStallRecoveryManager extends Handler { /** The data stall recovered by user (Mobile Data Power off/on). */ private static final int RECOVERED_REASON_USER = 3; + /** The number of milliseconds to wait for the DSRM prediction to complete. */ + private static final int DSRM_PREDICT_WAITING_MILLIS = 1000; + + /** Event for send data stall broadcast. */ + private static final int EVENT_SEND_DATA_STALL_BROADCAST = 1; + /** Event for triggering recovery action. */ private static final int EVENT_DO_RECOVERY = 2; /** Event for radio state changed. */ private static final int EVENT_RADIO_STATE_CHANGED = 3; + /** Event for recovery actions changed. */ + private static final int EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED = 4; + + /** Event for duration milliseconds changed. */ + private static final int EVENT_CONTENT_DSRM_DURATION_MILLIS_CHANGED = 5; + private final @NonNull Phone mPhone; private final @NonNull String mLogTag; private final @NonNull LocalLog mLocalLog = new LocalLog(128); @@ -185,8 +201,30 @@ public class DataStallRecoveryManager extends Handler { /** The boolean array for the flags. They are used to skip the recovery actions if needed. */ private @NonNull boolean[] mSkipRecoveryActionArray; + /** + * The content URI for the DSRM recovery actions. + * + * @see Settings.Global#DSRM_ENABLED_ACTIONS + */ + private static final Uri CONTENT_URL_DSRM_ENABLED_ACTIONS = Settings.Global.getUriFor( + Settings.Global.DSRM_ENABLED_ACTIONS); + + /** + * The content URI for the DSRM duration milliseconds. + * + * @see Settings.Global#DSRM_DURATION_MILLIS + */ + private static final Uri CONTENT_URL_DSRM_DURATION_MILLIS = Settings.Global.getUriFor( + Settings.Global.DSRM_DURATION_MILLIS); + + private DataStallRecoveryManagerCallback mDataStallRecoveryManagerCallback; + private final DataStallRecoveryStats mStats; + + /** The number of milliseconds to wait for the DSRM prediction to complete. */ + private @ElapsedRealtimeLong long mPredictWaitingMillis = 0L; + /** * The data stall recovery manager callback. Note this is only used for passing information * internally in the data stack, should not be used externally. @@ -248,6 +286,8 @@ public class DataStallRecoveryManager extends Handler { updateDataStallRecoveryConfigs(); registerAllEvents(); + + mStats = new DataStallRecoveryStats(mPhone, dataNetworkController); } /** Register for all events that data stall monitor is interested. */ @@ -280,12 +320,33 @@ public class DataStallRecoveryManager extends Handler { } }); mPhone.mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null); + + // Register for DSRM global setting changes. + mPhone.getContext().getContentResolver().registerContentObserver( + CONTENT_URL_DSRM_ENABLED_ACTIONS, false, mContentObserver); + mPhone.getContext().getContentResolver().registerContentObserver( + CONTENT_URL_DSRM_DURATION_MILLIS, false, mContentObserver); + } @Override public void handleMessage(Message msg) { logv("handleMessage = " + msg); switch (msg.what) { + case EVENT_SEND_DATA_STALL_BROADCAST: + mRecoveryTriggered = true; + // Verify that DSRM needs to process the recovery action + if (!isRecoveryNeeded(false)) { + cancelNetworkCheckTimer(); + startNetworkCheckTimer(getRecoveryAction()); + return; + } + // Broadcast intent that data stall has been detected. + broadcastDataStallDetected(getRecoveryAction()); + // Schedule the message to to wait for the predicted value. + sendMessageDelayed( + obtainMessage(EVENT_DO_RECOVERY), mPredictWaitingMillis); + break; case EVENT_DO_RECOVERY: doRecovery(); break; @@ -302,16 +363,119 @@ public class DataStallRecoveryManager extends Handler { } } break; + case EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED: + updateGlobalConfigActions(); + break; + case EVENT_CONTENT_DSRM_DURATION_MILLIS_CHANGED: + updateGlobalConfigDurations(); + break; default: loge("Unexpected message = " + msg); break; } } + private final ContentObserver mContentObserver = new ContentObserver(this) { + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + if (CONTENT_URL_DSRM_ENABLED_ACTIONS.equals(uri)) { + log("onChange URI: " + uri); + sendMessage(obtainMessage(EVENT_CONTENT_DSRM_ENABLED_ACTIONS_CHANGED)); + } else if (CONTENT_URL_DSRM_DURATION_MILLIS.equals(uri)) { + log("onChange URI: " + uri); + sendMessage(obtainMessage(EVENT_CONTENT_DSRM_DURATION_MILLIS_CHANGED)); + } + } + }; + + + @VisibleForTesting + public ContentObserver getContentObserver() { + return mContentObserver; + } + + /** + * Updates the skip recovery action array based on DSRM global configuration actions. + * + * @see Settings.Global#DSRM_ENABLED_ACTIONS + */ + private void updateGlobalConfigActions() { + String enabledActions = Settings.Global.getString( + mPhone.getContext().getContentResolver(), + Settings.Global.DSRM_ENABLED_ACTIONS); + + if (!TextUtils.isEmpty(enabledActions)) { + String[] splitEnabledActions = enabledActions.split(","); + boolean[] enabledActionsArray = new boolean[splitEnabledActions.length]; + for (int i = 0; i < enabledActionsArray.length; i++) { + enabledActionsArray[i] = Boolean.parseBoolean(splitEnabledActions[i].trim()); + } + + int minLength = Math.min(enabledActionsArray.length, + mSkipRecoveryActionArray.length); + + // Update the skip recovery action array. + for (int i = 0; i < minLength; i++) { + mSkipRecoveryActionArray[i] = !enabledActionsArray[i]; + } + log("SkipRecoveryAction: " + + Arrays.toString(mSkipRecoveryActionArray)); + mPredictWaitingMillis = DSRM_PREDICT_WAITING_MILLIS; + } else { + mPredictWaitingMillis = 0; + log("Enabled actions is null"); + } + } + + /** + * Updates the duration millis array based on DSRM global configuration durations. + * + * @see Settings.Global#DSRM_DURATION_MILLIS + */ + private void updateGlobalConfigDurations() { + String durationMillis = Settings.Global.getString( + mPhone.getContext().getContentResolver(), + Settings.Global.DSRM_DURATION_MILLIS); + + if (!TextUtils.isEmpty(durationMillis)) { + String[] splitDurationMillis = durationMillis.split(","); + long[] durationMillisArray = new long[splitDurationMillis.length]; + for (int i = 0; i < durationMillisArray.length; i++) { + try { + durationMillisArray[i] = Long.parseLong(splitDurationMillis[i].trim()); + } catch (NumberFormatException e) { + mPredictWaitingMillis = 0; + loge("Parsing duration millis error"); + return; + } + } + + int minLength = Math.min(durationMillisArray.length, + mDataStallRecoveryDelayMillisArray.length); + + // Copy the values from the durationMillisArray array to the + // mDataStallRecoveryDelayMillisArray array. + for (int i = 0; i < minLength; i++) { + mDataStallRecoveryDelayMillisArray[i] = durationMillisArray[i]; + } + log("DataStallRecoveryDelayMillis: " + + Arrays.toString(mDataStallRecoveryDelayMillisArray)); + mPredictWaitingMillis = DSRM_PREDICT_WAITING_MILLIS; + } else { + mPredictWaitingMillis = 0; + log("Duration millis is null"); + } + } + /** Update the data stall recovery configs from DataConfigManager. */ private void updateDataStallRecoveryConfigs() { mDataStallRecoveryDelayMillisArray = mDataConfigManager.getDataStallRecoveryDelayMillis(); mSkipRecoveryActionArray = mDataConfigManager.getDataStallRecoveryShouldSkipArray(); + + //Update Global settings + updateGlobalConfigActions(); + updateGlobalConfigDurations(); } /** @@ -320,7 +484,8 @@ public class DataStallRecoveryManager extends Handler { * @param recoveryAction The recovery action to query. * @return the delay in milliseconds for the specific recovery action. */ - private long getDataStallRecoveryDelayMillis(@RecoveryAction int recoveryAction) { + @VisibleForTesting + public long getDataStallRecoveryDelayMillis(@RecoveryAction int recoveryAction) { return mDataStallRecoveryDelayMillisArray[recoveryAction]; } @@ -330,7 +495,8 @@ public class DataStallRecoveryManager extends Handler { * @param recoveryAction The recovery action. * @return {@code true} if the action needs to be skipped. */ - private boolean shouldSkipRecoveryAction(@RecoveryAction int recoveryAction) { + @VisibleForTesting + public boolean shouldSkipRecoveryAction(@RecoveryAction int recoveryAction) { return mSkipRecoveryActionArray[recoveryAction]; } @@ -385,7 +551,7 @@ public class DataStallRecoveryManager extends Handler { mIsValidNetwork = false; log("trigger data stall recovery"); mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime(); - sendMessage(obtainMessage(EVENT_DO_RECOVERY)); + sendMessage(obtainMessage(EVENT_SEND_DATA_STALL_BROADCAST)); } } @@ -490,6 +656,22 @@ public class DataStallRecoveryManager extends Handler { Intent intent = new Intent(TelephonyManager.ACTION_DATA_STALL_DETECTED); SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId()); intent.putExtra(TelephonyManager.EXTRA_RECOVERY_ACTION, recoveryAction); + + // Get the information for DSRS state + final boolean isRecovered = !mDataStalled; + final int duration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs); + final @RecoveredReason int reason = getRecoveredReason(mIsValidNetwork); + final boolean isFirstValidationOfAction = false; + final int durationOfAction = (int) getDurationOfCurrentRecoveryMs(); + + // Get the bundled DSRS stats. + Bundle bundle = mStats.getDataStallRecoveryMetricsData( + recoveryAction, isRecovered, duration, reason, isFirstValidationOfAction, + durationOfAction); + + // Put the bundled stats extras on the intent. + intent.putExtra("EXTRA_DSRS_STATS_BUNDLE", bundle); + mPhone.getContext().sendBroadcast(intent, READ_PRIVILEGED_PHONE_STATE); } @@ -531,7 +713,8 @@ public class DataStallRecoveryManager extends Handler { mNetworkCheckTimerStarted = true; mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime(); sendMessageDelayed( - obtainMessage(EVENT_DO_RECOVERY), getDataStallRecoveryDelayMillis(action)); + obtainMessage(EVENT_SEND_DATA_STALL_BROADCAST), + getDataStallRecoveryDelayMillis(action)); } } @@ -540,7 +723,7 @@ public class DataStallRecoveryManager extends Handler { log("cancelNetworkCheckTimer()"); if (mNetworkCheckTimerStarted) { mNetworkCheckTimerStarted = false; - removeMessages(EVENT_DO_RECOVERY); + removeMessages(EVENT_SEND_DATA_STALL_BROADCAST); } } @@ -645,8 +828,9 @@ public class DataStallRecoveryManager extends Handler { && !mIsAttemptedAllSteps) || mLastAction == RECOVERY_ACTION_RESET_MODEM) ? (int) getDurationOfCurrentRecoveryMs() : 0; - DataStallRecoveryStats.onDataStallEvent( - mLastAction, mPhone, isValid, timeDuration, reason, + + mStats.uploadMetrics( + mLastAction, isValid, timeDuration, reason, isFirstValidationAfterDoRecovery, timeDurationOfCurrentAction); logl( "data stall: " @@ -696,22 +880,12 @@ public class DataStallRecoveryManager extends Handler { private void doRecovery() { @RecoveryAction final int recoveryAction = getRecoveryAction(); final int signalStrength = mPhone.getSignalStrength().getLevel(); - mRecoveryTriggered = true; - - // DSRM used sendMessageDelayed to process the next event EVENT_DO_RECOVERY, so it need - // to check the condition if DSRM need to process the recovery action. - if (!isRecoveryNeeded(false)) { - cancelNetworkCheckTimer(); - startNetworkCheckTimer(recoveryAction); - return; - } TelephonyMetrics.getInstance() .writeSignalStrengthEvent(mPhone.getPhoneId(), signalStrength); TelephonyMetrics.getInstance().writeDataStallEvent(mPhone.getPhoneId(), recoveryAction); mLastAction = recoveryAction; mLastActionReported = false; - broadcastDataStallDetected(recoveryAction); mNetworkCheckTimerStarted = false; mTimeElapsedOfCurrentAction = SystemClock.elapsedRealtime(); @@ -873,6 +1047,7 @@ public class DataStallRecoveryManager extends Handler { pw.println( "mMobileDataChangedToEnabledDuringDataStall=" + mMobileDataChangedToEnabledDuringDataStall); + pw.println("mPredictWaitingMillis=" + mPredictWaitingMillis); pw.println( "DataStallRecoveryDelayMillisArray=" + Arrays.toString(mDataStallRecoveryDelayMillisArray)); diff --git a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java index 5ed12aadec..de8c48c74f 100644 --- a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java +++ b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java @@ -315,6 +315,8 @@ public class LinkBandwidthEstimator extends Handler { registerNrStateFrequencyChange(); mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(AccessNetworkConstants .TRANSPORT_TYPE_WWAN, this, MSG_DATA_REG_STATE_OR_RAT_CHANGED, null); + mPhone.getSignalStrengthController().registerForSignalStrengthChanged(this, + MSG_SIGNAL_STRENGTH_CHANGED, null); } @Override @@ -333,7 +335,7 @@ public class LinkBandwidthEstimator extends Handler { handleDefaultNetworkChanged((NetworkCapabilities) msg.obj); break; case MSG_SIGNAL_STRENGTH_CHANGED: - handleSignalStrengthChanged((SignalStrength) msg.obj); + handleSignalStrengthChanged(); break; case MSG_NR_FREQUENCY_CHANGED: // fall through @@ -917,10 +919,8 @@ public class LinkBandwidthEstimator extends Handler { () -> callback.onBandwidthChanged(linkBandwidthTxKps, linkBandwidthRxKps))); } - private void handleSignalStrengthChanged(SignalStrength signalStrength) { - if (signalStrength == null) { - return; - } + private void handleSignalStrengthChanged() { + SignalStrength signalStrength = mPhone.getSignalStrength(); mSignalStrengthDbm = signalStrength.getDbm(); mSignalLevel = signalStrength.getLevel(); @@ -1099,13 +1099,8 @@ public class LinkBandwidthEstimator extends Handler { } private class TelephonyCallbackImpl extends TelephonyCallback implements - TelephonyCallback.SignalStrengthsListener, TelephonyCallback.ActiveDataSubscriptionIdListener { @Override - public void onSignalStrengthsChanged(SignalStrength signalStrength) { - obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, signalStrength).sendToTarget(); - } - @Override public void onActiveDataSubscriptionIdChanged(int subId) { obtainMessage(MSG_ACTIVE_PHONE_CHANGED, subId).sendToTarget(); } diff --git a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java index d2fdf4abb9..cf1a47d3f6 100644 --- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java +++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java @@ -18,6 +18,7 @@ package com.android.internal.telephony.data; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG; +import static android.telephony.SubscriptionManager.DEFAULT_PHONE_INDEX; import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -27,14 +28,10 @@ import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATIO import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM; import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN; import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE; - import static java.util.Arrays.copyOf; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.Context; @@ -50,7 +47,6 @@ import android.net.NetworkSpecifier; import android.net.TelephonyNetworkSpecifier; import android.os.AsyncResult; import android.os.Build; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -58,10 +54,7 @@ import android.os.PersistableBundle; import android.os.Registrant; import android.os.RegistrantList; import android.os.RemoteException; -import android.provider.Settings; -import android.telephony.AccessNetworkConstants; import android.telephony.CarrierConfigManager; -import android.telephony.NetworkRegistrationInfo; import android.telephony.PhoneCapability; import android.telephony.PhoneStateListener; import android.telephony.SubscriptionInfo; @@ -79,6 +72,7 @@ import android.util.Log; import com.android.ims.ImsException; import com.android.ims.ImsManager; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Call; import com.android.internal.telephony.CommandException; import com.android.internal.telephony.ISetOpportunisticDataCallback; import com.android.internal.telephony.IccCard; @@ -96,7 +90,6 @@ import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.OnDeman import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.subscription.SubscriptionManagerService.WatchedInt; -import com.android.internal.telephony.util.NotificationChannelController; import com.android.internal.util.IndentingPrintWriter; import com.android.telephony.Rlog; @@ -121,21 +114,6 @@ public class PhoneSwitcher extends Handler { private static final String LOG_TAG = "PhoneSwitcher"; protected static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE); - /** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */ - private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; - /** - * When starting this activity, this extra can also be specified to supply a Bundle of arguments - * to pass to that fragment when it is instantiated during the initial creation of the activity. - */ - private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS = - ":settings:show_fragment_args"; - /** The res Id of the auto data switch fragment in settings. **/ - private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch"; - /** Notification tag **/ - private static final String AUTO_DATA_SWITCH_NOTIFICATION_TAG = "auto_data_switch"; - /** Notification ID **/ - private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1; - private static final int MODEM_COMMAND_RETRY_PERIOD_MS = 5000; // After the emergency call ends, wait for a few seconds to see if we enter ECBM before starting // the countdown to remove the emergency DDS override. @@ -257,9 +235,6 @@ public class PhoneSwitcher extends Handler { // its value will be DEFAULT_SUBSCRIPTION_ID. private int mAutoSelectedDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; - /** The count of consecutive auto switch validation failure **/ - private int mAutoSwitchRetryFailedCount = 0; - // The phone ID that has an active voice call. If set, and its mobile data setting is on, // it will become the mPreferredDataPhoneId. protected int mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX; @@ -302,10 +277,10 @@ public class PhoneSwitcher extends Handler { // ECBM, which is detected by EVENT_EMERGENCY_TOGGLE. private static final int EVENT_PRECISE_CALL_STATE_CHANGED = 109; private static final int EVENT_NETWORK_VALIDATION_DONE = 110; - private static final int EVENT_EVALUATE_AUTO_SWITCH = 111; + private static final int EVENT_MODEM_COMMAND_DONE = 112; private static final int EVENT_MODEM_COMMAND_RETRY = 113; - private static final int EVENT_SERVICE_STATE_CHANGED = 114; + // An emergency call is about to be originated and requires the DDS to be overridden. // Uses EVENT_PRECISE_CALL_STATE_CHANGED message to start countdown to finish override defined // in mEmergencyOverride. If EVENT_PRECISE_CALL_STATE_CHANGED does not come in @@ -318,7 +293,6 @@ public class PhoneSwitcher extends Handler { private static final int EVENT_NETWORK_AVAILABLE = 118; private static final int EVENT_PROCESS_SIM_STATE_CHANGE = 119; private static final int EVENT_IMS_RADIO_TECH_CHANGED = 120; - private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE = 121; // List of events triggers re-evaluations private static final String EVALUATION_REASON_RADIO_ON = "EVENT_RADIO_ON"; @@ -343,23 +317,9 @@ public class PhoneSwitcher extends Handler { private List<Set<CommandException.Error>> mCurrentDdsSwitchFailure; - /** - * {@code true} if requires ping test before switching preferred data modem; otherwise, switch - * even if ping test fails. - */ - private boolean mRequirePingTestBeforeDataSwitch = true; - - /** - * Time threshold in ms to define a internet connection status to be stable(e.g. out of service, - * in service, wifi is the default active network.etc), while -1 indicates auto switch - * feature disabled. - */ - private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1; - - /** - * The maximum number of retries when a validation for switching failed. - */ - private int mAutoDataSwitchValidationMaxRetry; + private AutoDataSwitchController mAutoDataSwitchController; + private AutoDataSwitchController.AutoDataSwitchControllerCallback + mAutoDataSwitchCallback; /** Data settings manager callback. Key is the phone id. */ private final @NonNull Map<Integer, DataSettingsManagerCallback> mDataSettingsManagerCallbacks = @@ -368,12 +328,10 @@ public class PhoneSwitcher extends Handler { private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback { public int mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; public int mSwitchReason = TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN; - public boolean isDefaultNetworkOnCellular = false; @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) { - isDefaultNetworkOnCellular = true; if (SubscriptionManager.isValidSubscriptionId(mExpectedSubId) && mExpectedSubId == getSubIdFromNetworkSpecifier( networkCapabilities.getNetworkSpecifier())) { @@ -384,22 +342,13 @@ public class PhoneSwitcher extends Handler { mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; mSwitchReason = TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN; } - } else { - if (isDefaultNetworkOnCellular) { - // non-cellular transport is active - isDefaultNetworkOnCellular = false; - log("default network is active on non cellular"); - evaluateIfAutoSwitchIsNeeded(); - } } + mAutoDataSwitchController.updateDefaultNetworkCapabilities(networkCapabilities); } @Override public void onLost(Network network) { - // try find an active sub to switch to - if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) { - sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH); - } + mAutoDataSwitchController.updateDefaultNetworkCapabilities(null); } } @@ -551,8 +500,6 @@ public class PhoneSwitcher extends Handler { }}); phone.getDataSettingsManager().registerCallback( mDataSettingsManagerCallbacks.get(phoneId)); - phone.getServiceStateTracker().registerForServiceStateChanged(this, - EVENT_SERVICE_STATE_CHANGED, phoneId); registerForImsRadioTechChange(context, phoneId); } Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>(); @@ -563,8 +510,6 @@ public class PhoneSwitcher extends Handler { PhoneFactory.getPhone(0).mCi.registerForOn(this, EVENT_RADIO_ON, null); } - readDeviceResourceConfig(); - TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager) context.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); telephonyRegistryManager.addOnSubscriptionsChangedListener( @@ -573,6 +518,36 @@ public class PhoneSwitcher extends Handler { mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + mAutoDataSwitchCallback = new AutoDataSwitchController.AutoDataSwitchControllerCallback() { + @Override + public void onRequireValidation(int targetPhoneId, boolean needValidation) { + int targetSubId = targetPhoneId == DEFAULT_PHONE_INDEX + ? DEFAULT_SUBSCRIPTION_ID + : mSubscriptionManagerService.getSubId(targetPhoneId); + PhoneSwitcher.this.validate(targetSubId, needValidation, + DataSwitch.Reason.DATA_SWITCH_REASON_AUTO, null); + } + + @Override + public void onRequireImmediatelySwitchToPhone(int targetPhoneId, + @AutoDataSwitchController.AutoDataSwitchEvaluationReason int reason) { + PhoneSwitcher.this.mAutoSelectedDataSubId = + targetPhoneId == DEFAULT_PHONE_INDEX + ? DEFAULT_SUBSCRIPTION_ID + : mSubscriptionManagerService.getSubId(targetPhoneId); + PhoneSwitcher.this.evaluateIfImmediateDataSwitchIsNeeded( + AutoDataSwitchController.evaluationReasonToString(reason), + DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL); + } + + @Override + public void onRequireCancelAnyPendingAutoSwitchValidation() { + PhoneSwitcher.this.cancelPendingAutoDataSwitchValidation(); + } + }; + mAutoDataSwitchController = new AutoDataSwitchController(context, looper, this, + mAutoDataSwitchCallback); + mContext.registerReceiver(mDefaultDataChangedReceiver, new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)); @@ -675,19 +650,6 @@ public class PhoneSwitcher extends Handler { onEvaluate(REQUESTS_UNCHANGED, "subscription changed"); break; } - case EVENT_SERVICE_STATE_CHANGED: { - AsyncResult ar = (AsyncResult) msg.obj; - final int phoneId = (int) ar.userObj; - onServiceStateChanged(phoneId); - break; - } - case EVENT_MEETS_AUTO_DATA_SWITCH_STATE: { - final int targetSubId = msg.arg1; - final boolean needValidation = (boolean) msg.obj; - validate(targetSubId, needValidation, - DataSwitch.Reason.DATA_SWITCH_REASON_AUTO, null); - break; - } case EVENT_PRIMARY_DATA_SUB_CHANGED: { evaluateIfImmediateDataSwitchIsNeeded("primary data sub changed", DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL); @@ -718,9 +680,6 @@ public class PhoneSwitcher extends Handler { onEvaluate(REQUESTS_CHANGED, "emergencyToggle"); break; } - case EVENT_EVALUATE_AUTO_SWITCH: - evaluateIfAutoSwitchIsNeeded(); - break; case EVENT_RADIO_CAPABILITY_CHANGED: { final int phoneId = msg.arg1; sendRilCommands(phoneId); @@ -792,7 +751,8 @@ public class PhoneSwitcher extends Handler { DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL); if (!isAnyVoiceCallActiveOnDevice()) { // consider auto switch on hang up all voice call - evaluateIfAutoSwitchIsNeeded(); + mAutoDataSwitchController.evaluateAutoDataSwitch( + AutoDataSwitchController.EVALUATION_REASON_VOICE_CALL_END); } break; } @@ -892,33 +852,19 @@ public class PhoneSwitcher extends Handler { } else if (TelephonyManager.SIM_STATE_LOADED == simState) { if (mCurrentDdsSwitchFailure.get(slotIndex).contains( CommandException.Error.INVALID_SIM_STATE) - && (TelephonyManager.SIM_STATE_LOADED == simState) && isSimApplicationReady(slotIndex)) { sendRilCommands(slotIndex); } // SIM loaded after subscriptions slot mapping are done. Evaluate for auto // data switch. - sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH); + mAutoDataSwitchController.evaluateAutoDataSwitch( + AutoDataSwitchController.EVALUATION_REASON_SIM_LOADED); } break; } } } - /** - * Read the default device config from any default phone because the resource config are per - * device. No need to register callback for the same reason. - */ - private void readDeviceResourceConfig() { - Phone phone = PhoneFactory.getDefaultPhone(); - DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager(); - mRequirePingTestBeforeDataSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired(); - mAutoDataSwitchAvailabilityStabilityTimeThreshold = - dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold(); - mAutoDataSwitchValidationMaxRetry = - dataConfig.getAutoDataSwitchValidationMaxRetry(); - } - private synchronized void onMultiSimConfigChanged(int activeModemCount) { // No change. if (mActiveModemCount == activeModemCount) return; @@ -958,13 +904,13 @@ public class PhoneSwitcher extends Handler { }); phone.getDataSettingsManager().registerCallback( mDataSettingsManagerCallbacks.get(phone.getPhoneId())); - phone.getServiceStateTracker().registerForServiceStateChanged(this, - EVENT_SERVICE_STATE_CHANGED, phoneId); Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>(); mCurrentDdsSwitchFailure.add(ddsFailure); registerForImsRadioTechChange(mContext, phoneId); } + + mAutoDataSwitchController.onMultiSimConfigChanged(activeModemCount); } /** @@ -973,14 +919,14 @@ public class PhoneSwitcher extends Handler { * 2. OR user changed auto data switch feature */ private void onDataEnabledChanged() { - logl("user changed data related settings"); if (isAnyVoiceCallActiveOnDevice()) { // user changed data related settings during call, switch or turn off immediately evaluateIfImmediateDataSwitchIsNeeded( "user changed data settings during call", DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL); } else { - evaluateIfAutoSwitchIsNeeded(); + mAutoDataSwitchController.evaluateAutoDataSwitch(AutoDataSwitchController + .EVALUATION_REASON_DATA_SETTINGS_CHANGED); } } @@ -1069,134 +1015,9 @@ public class PhoneSwitcher extends Handler { } /** - * Called when service state changed. - */ - private void onServiceStateChanged(int phoneId) { - Phone phone = findPhoneById(phoneId); - if (phone != null) { - int newRegState = phone.getServiceState() - .getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_PS, - AccessNetworkConstants.TRANSPORT_TYPE_WWAN) - .getRegistrationState(); - if (newRegState != mPhoneStates[phoneId].dataRegState) { - mPhoneStates[phoneId].dataRegState = newRegState; - logl("onServiceStateChanged: phoneId:" + phoneId + " dataReg-> " - + NetworkRegistrationInfo.registrationStateToString(newRegState)); - if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) { - sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH); - } - } - } - } - - /** - * Evaluate if auto switch is suitable at the moment. - */ - private void evaluateIfAutoSwitchIsNeeded() { - // auto data switch feature is disabled from server - if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return; - // check is valid DSDS - if (!isActiveSubId(mPrimaryDataSubId) || mSubscriptionManagerService - .getActiveSubIdList(true).length <= 1) { - return; - } - - Phone primaryDataPhone = getPhoneBySubId(mPrimaryDataSubId); - if (primaryDataPhone == null) { - loge("evaluateIfAutoSwitchIsNeeded: cannot find primary data phone. subId=" - + mPrimaryDataSubId); - return; - } - - int primaryPhoneId = primaryDataPhone.getPhoneId(); - log("evaluateIfAutoSwitchIsNeeded: primaryPhoneId: " + primaryPhoneId - + " preferredPhoneId: " + mPreferredDataPhoneId); - Phone secondaryDataPhone; - - if (mPreferredDataPhoneId == primaryPhoneId) { - // on primary data sub - - int candidateSubId = getAutoSwitchTargetSubIdIfExists(); - if (candidateSubId != INVALID_SUBSCRIPTION_ID) { - startAutoDataSwitchStabilityCheck(candidateSubId, mRequirePingTestBeforeDataSwitch); - } else { - cancelPendingAutoDataSwitch(); - } - } else if ((secondaryDataPhone = findPhoneById(mPreferredDataPhoneId)) != null) { - // on secondary data sub - - if (!primaryDataPhone.isUserDataEnabled() - || !secondaryDataPhone.isDataAllowed()) { - // immediately switch back if user setting changes - mAutoSelectedDataSubId = DEFAULT_SUBSCRIPTION_ID; - evaluateIfImmediateDataSwitchIsNeeded("User disabled data settings", - DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL); - return; - } - - NetworkCapabilities defaultNetworkCapabilities = mConnectivityManager - .getNetworkCapabilities(mConnectivityManager.getActiveNetwork()); - if (defaultNetworkCapabilities != null && !defaultNetworkCapabilities - .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - log("evaluateIfAutoSwitchIsNeeded: " - + "Default network is active on non-cellular transport"); - startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID, false); - return; - } - - if (mPhoneStates[secondaryDataPhone.getPhoneId()].dataRegState - != NetworkRegistrationInfo.REGISTRATION_STATE_HOME) { - // secondary phone lost its HOME availability - startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID, false); - return; - } - - if (isInService(mPhoneStates[primaryPhoneId])) { - // primary becomes available - startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID, - mRequirePingTestBeforeDataSwitch); - return; - } - - // cancel any previous attempts of switching back to primary - cancelPendingAutoDataSwitch(); - } - } - - /** - * @param phoneState The phone state to check - * @return {@code true} if the phone state is considered in service. - */ - private boolean isInService(@NonNull PhoneState phoneState) { - return phoneState.dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME - || phoneState.dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING; - } - - /** - * Called when the current environment suits auto data switch. - * Start pre-switch validation if the current environment suits auto data switch for - * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS. - * @param targetSubId the target sub Id. - * @param needValidation {@code true} if validation is needed. - */ - private void startAutoDataSwitchStabilityCheck(int targetSubId, boolean needValidation) { - log("startAutoDataSwitchStabilityCheck: targetSubId=" + targetSubId - + " needValidation=" + needValidation); - if (!hasMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, needValidation)) { - sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetSubId, - 0/*placeholder*/, - needValidation), - mAutoDataSwitchAvailabilityStabilityTimeThreshold); - } - } - - /** * Cancel any auto switch attempts when the current environment is not suitable for auto switch. */ - private void cancelPendingAutoDataSwitch() { - mAutoSwitchRetryFailedCount = 0; - removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE); + private void cancelPendingAutoDataSwitchValidation() { if (mValidator.isValidating()) { mValidator.stopValidation(); @@ -1207,56 +1028,6 @@ public class PhoneSwitcher extends Handler { } } - /** - * Called when consider switching from primary default data sub to another data sub. - * @return the target subId if a suitable candidate is found, otherwise return - * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} - */ - private int getAutoSwitchTargetSubIdIfExists() { - Phone primaryDataPhone = getPhoneBySubId(mPrimaryDataSubId); - if (primaryDataPhone == null) { - log("getAutoSwitchTargetSubId: no sim loaded"); - return INVALID_SUBSCRIPTION_ID; - } - - int primaryPhoneId = primaryDataPhone.getPhoneId(); - - if (!primaryDataPhone.isUserDataEnabled()) { - log("getAutoSwitchTargetSubId: user disabled data"); - return INVALID_SUBSCRIPTION_ID; - } - - NetworkCapabilities defaultNetworkCapabilities = mConnectivityManager - .getNetworkCapabilities(mConnectivityManager.getActiveNetwork()); - if (defaultNetworkCapabilities != null && !defaultNetworkCapabilities - .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - // Exists other active default transport - log("getAutoSwitchTargetSubId: Default network is active on non-cellular transport"); - return INVALID_SUBSCRIPTION_ID; - } - - // check whether primary and secondary signal status worth switching - if (isInService(mPhoneStates[primaryPhoneId])) { - log("getAutoSwitchTargetSubId: primary is in service"); - return INVALID_SUBSCRIPTION_ID; - } - for (int phoneId = 0; phoneId < mPhoneStates.length; phoneId++) { - if (phoneId != primaryPhoneId) { - // the alternative phone must have HOME availability - if (mPhoneStates[phoneId].dataRegState - == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) { - log("getAutoSwitchTargetSubId: found phone " + phoneId + " in HOME service"); - Phone secondaryDataPhone = findPhoneById(phoneId); - if (secondaryDataPhone != null && // check auto switch feature enabled - secondaryDataPhone.isDataAllowed()) { - return secondaryDataPhone.getSubId(); - } - } - } - } - return INVALID_SUBSCRIPTION_ID; - } - private TelephonyManager getTm() { return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); } @@ -1415,8 +1186,6 @@ public class PhoneSwitcher extends Handler { protected static class PhoneState { public volatile boolean active = false; - public @NetworkRegistrationInfo.RegistrationState int dataRegState = - NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; public long lastRequested = 0; } @@ -1836,14 +1605,12 @@ public class PhoneSwitcher extends Handler { if (!isActiveSubId(subId)) { logl("confirmSwitch: subId " + subId + " is no longer active"); resultForCallBack = SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION; - mAutoSwitchRetryFailedCount = 0; } else if (!confirm) { resultForCallBack = SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED; // retry for auto data switch validation failure if (mLastSwitchPreferredDataReason == DataSwitch.Reason.DATA_SWITCH_REASON_AUTO) { - scheduleAutoSwitchRetryEvaluation(); - mAutoSwitchRetryFailedCount++; + mAutoDataSwitchController.evaluateRetryOnValidationFailed(); } } else { if (subId == mPrimaryDataSubId) { @@ -1852,7 +1619,7 @@ public class PhoneSwitcher extends Handler { setAutoSelectedDataSubIdInternal(subId); } resultForCallBack = SET_OPPORTUNISTIC_SUB_SUCCESS; - mAutoSwitchRetryFailedCount = 0; + mAutoDataSwitchController.resetFailedCount(); } // Trigger callback if needed @@ -1861,23 +1628,6 @@ public class PhoneSwitcher extends Handler { mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID; } - /** - * Schedule auto data switch evaluation retry if haven't reached the max retry count. - */ - private void scheduleAutoSwitchRetryEvaluation() { - if (mAutoSwitchRetryFailedCount < mAutoDataSwitchValidationMaxRetry) { - if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) { - sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH), - mAutoDataSwitchAvailabilityStabilityTimeThreshold - << mAutoSwitchRetryFailedCount); - } - } else { - logl("scheduleAutoSwitchEvaluation: reached max auto switch retry count " - + mAutoDataSwitchValidationMaxRetry); - mAutoSwitchRetryFailedCount = 0; - } - } - private void onNetworkAvailable(int subId, Network network) { log("onNetworkAvailable: on subId " + subId); // Do nothing unless pending switch matches target subId and it doesn't require @@ -1928,8 +1678,14 @@ public class PhoneSwitcher extends Handler { } // A phone in voice call might trigger data being switched to it. + // Exclude dialing to give modem time to process an EMC first before dealing with DDS switch + // Include alerting because modem RLF leads to delay in switch, so carrier required to + // switch in alerting phase. + // TODO: check ringing call for vDADA return (!phone.getBackgroundCall().isIdle() - || !phone.getForegroundCall().isIdle()); + && phone.getBackgroundCall().getState() != Call.State.DIALING) + || (!phone.getForegroundCall().isIdle() + && phone.getForegroundCall().getState() != Call.State.DIALING); } private void updateHalCommandToUse() { @@ -2062,8 +1818,7 @@ public class PhoneSwitcher extends Handler { for (int i = 0; i < mActiveModemCount; i++) { PhoneState ps = mPhoneStates[i]; c.setTimeInMillis(ps.lastRequested); - pw.println("PhoneId(" + i + ") active=" + ps.active + ", dataRegState=" - + NetworkRegistrationInfo.registrationStateToString(ps.dataRegState) + pw.println("PhoneId(" + i + ") active=" + ps.active + ", lastRequest=" + (ps.lastRequested == 0 ? "never" : String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c))); @@ -2081,10 +1836,6 @@ public class PhoneSwitcher extends Handler { pw.println("mActiveModemCount=" + mActiveModemCount); pw.println("mPhoneIdInVoiceCall=" + mPhoneIdInVoiceCall); pw.println("mCurrentDdsSwitchFailure=" + mCurrentDdsSwitchFailure); - pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold=" - + mAutoDataSwitchAvailabilityStabilityTimeThreshold); - pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry); - pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeDataSwitch); pw.println("mLastSwitchPreferredDataReason=" + switchReasonToString(mLastSwitchPreferredDataReason)); pw.println("mDisplayedAutoSwitchNotification=" + mDisplayedAutoSwitchNotification); @@ -2092,6 +1843,7 @@ public class PhoneSwitcher extends Handler { pw.increaseIndent(); mLocalLog.dump(fd, pw, args); pw.decreaseIndent(); + mAutoDataSwitchController.dump(fd, pw, args); pw.decreaseIndent(); } @@ -2130,72 +1882,15 @@ public class PhoneSwitcher extends Handler { phoneId), MODEM_COMMAND_RETRY_PERIOD_MS); return; } - if (commandSuccess) logl("onDdsSwitchResponse: DDS switch success on phoneId = " + phoneId); + if (commandSuccess) { + logl("onDdsSwitchResponse: DDS switch success on phoneId = " + phoneId); + mAutoDataSwitchController.displayAutoDataSwitchNotification(phoneId, + mLastSwitchPreferredDataReason == DataSwitch.Reason.DATA_SWITCH_REASON_AUTO); + } mCurrentDdsSwitchFailure.get(phoneId).clear(); // Notify all registrants mActivePhoneRegistrants.notifyRegistrants(); notifyPreferredDataSubIdChanged(); - displayAutoDataSwitchNotification(); - } - - /** - * Display a notification the first time auto data switch occurs. - */ - private void displayAutoDataSwitchNotification() { - NotificationManager notificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - - if (mDisplayedAutoSwitchNotification) { - // cancel posted notification if any exist - log("displayAutoDataSwitchNotification: canceling any notifications for subId " - + mAutoSelectedDataSubId); - notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG, - AUTO_DATA_SWITCH_NOTIFICATION_ID); - return; - } - // proceed only the first time auto data switch occurs, which includes data during call - if (mLastSwitchPreferredDataReason != DataSwitch.Reason.DATA_SWITCH_REASON_AUTO) { - log("displayAutoDataSwitchNotification: Ignore DDS switch due to " - + switchReasonToString(mLastSwitchPreferredDataReason)); - return; - } - SubscriptionInfo subInfo = mSubscriptionManagerService - .getSubscriptionInfo(mAutoSelectedDataSubId); - if (subInfo == null || subInfo.isOpportunistic()) { - loge("displayAutoDataSwitchNotification: mAutoSelectedDataSubId=" - + mAutoSelectedDataSubId + " unexpected subInfo " + subInfo); - return; - } - logl("displayAutoDataSwitchNotification: display for subId=" + mAutoSelectedDataSubId); - // "Mobile network settings" screen / dialog - Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS); - final Bundle fragmentArgs = new Bundle(); - // Special contract for Settings to highlight permission row - fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID); - intent.putExtra(Settings.EXTRA_SUB_ID, mAutoSelectedDataSubId); - intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs); - PendingIntent contentIntent = PendingIntent.getActivity( - mContext, mAutoSelectedDataSubId, intent, PendingIntent.FLAG_IMMUTABLE); - - CharSequence activeCarrierName = subInfo.getDisplayName(); - CharSequence contentTitle = mContext.getString( - com.android.internal.R.string.auto_data_switch_title, activeCarrierName); - CharSequence contentText = mContext.getText( - com.android.internal.R.string.auto_data_switch_content); - - final Notification notif = new Notification.Builder(mContext) - .setContentTitle(contentTitle) - .setContentText(contentText) - .setSmallIcon(android.R.drawable.stat_sys_warning) - .setColor(mContext.getResources().getColor( - com.android.internal.R.color.system_notification_accent_color)) - .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS) - .setContentIntent(contentIntent) - .setStyle(new Notification.BigTextStyle().bigText(contentText)) - .build(); - notificationManager.notify(AUTO_DATA_SWITCH_NOTIFICATION_TAG, - AUTO_DATA_SWITCH_NOTIFICATION_ID, notif); - mDisplayedAutoSwitchNotification = true; } private boolean isPhoneIdValidForRetry(int phoneId) { diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java index 9b44001785..e2418c5acd 100644 --- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java +++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java @@ -935,6 +935,23 @@ public class EmergencyNumberTracker extends Handler { } /** + * Get a list of the {@link EmergencyNumber}s that have the corresponding emergency number. + * Note: {@link #getEmergencyNumber(String)} assumes there is ONLY one record for a phone number + * when in reality there CAN be multiple instances if the same number is reported by the radio + * for a specific mcc and the emergency number database specifies the number without an mcc + * specified. + * + * @param emergencyNumber the emergency number to find. + * @return the list of emergency numbers matching. + */ + public List<EmergencyNumber> getEmergencyNumbers(String emergencyNumber) { + final String toFind = PhoneNumberUtils.stripSeparators(emergencyNumber); + return getEmergencyNumberList().stream() + .filter(num -> num.getNumber().equals(toFind)) + .toList(); + } + + /** * Get the emergency service categories for the corresponding emergency number. The only * trusted sources for the categories are the * {@link EmergencyNumber#EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING} and diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java index a5b95c35bf..c8f3368d07 100644 --- a/src/java/com/android/internal/telephony/euicc/EuiccController.java +++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java @@ -191,6 +191,19 @@ public class EuiccController extends IEuiccController.Stub { boolean usePortIndex = resolutionIntent.getBooleanExtra( EuiccService.EXTRA_RESOLUTION_USE_PORT_INDEX, false); resolutionExtras.putBoolean(EuiccService.EXTRA_RESOLUTION_USE_PORT_INDEX, usePortIndex); + + if (!EuiccService.ACTION_RESOLVE_NO_PRIVILEGES.equals(resolutionIntent.getAction()) + || !resolutionExtras.containsKey(EuiccService.EXTRA_RESOLUTION_PORT_INDEX)) { + // Port index resolution is requested only through the ACTION_RESOLVE_NO_PRIVILEGES + // action. Therefore, if the action is not ACTION_RESOLVE_NO_PRIVILEGES, use the + // port index from the resolution intent. + // (OR) If the action is ACTION_RESOLVE_NO_PRIVILEGES and resolutionExtras does not + // contain the EXTRA_RESOLUTION_PORT_INDEX key, retrieve the port index from + // resolutionIntent. + resolutionExtras.putInt(EuiccService.EXTRA_RESOLUTION_PORT_INDEX, + resolutionIntent.getIntExtra(EuiccService.EXTRA_RESOLUTION_PORT_INDEX, + TelephonyManager.DEFAULT_PORT_INDEX)); + } Log.i(TAG, " continueOperation portIndex: " + resolutionExtras.getInt( EuiccService.EXTRA_RESOLUTION_PORT_INDEX) + " usePortIndex: " + usePortIndex); op.continueOperation(cardId, resolutionExtras, callbackIntent); diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java index 4b150cc533..3ab9fd7872 100644 --- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java @@ -139,6 +139,7 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; import java.util.function.Consumer; +import java.util.stream.Stream; /** * {@hide} @@ -2539,10 +2540,6 @@ public class ImsPhone extends ImsPhoneBase { /** Sets the IMS phone number from IMS associated URIs, if any found. */ @VisibleForTesting public void setPhoneNumberForSourceIms(Uri[] uris) { - String phoneNumber = extractPhoneNumberFromAssociatedUris(uris); - if (phoneNumber == null) { - return; - } int subId = getSubId(); if (!SubscriptionManager.isValidSubscriptionId(subId)) { // Defending b/219080264: @@ -2551,16 +2548,33 @@ public class ImsPhone extends ImsPhoneBase { // IMS callbacks are sent back to telephony after SIM state changed. return; } - SubscriptionInfoInternal subInfo = mSubscriptionManagerService .getSubscriptionInfoInternal(subId); - if (subInfo != null) { - phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber, - subInfo.getCountryIso()); + if (subInfo == null) { + loge("trigger setPhoneNumberForSourceIms, but subInfo is null"); + return; + } + String subCountryIso = subInfo.getCountryIso(); + String phoneNumber = extractPhoneNumberFromAssociatedUris(uris, /*isGlobalFormat*/true); + if (phoneNumber != null) { + phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber, subCountryIso); if (phoneNumber == null) { + loge("format to E164 failed"); return; } mSubscriptionManagerService.setNumberFromIms(subId, phoneNumber); + } else if (isAllowNonGlobalNumberFormat()) { + // If carrier config has true for KEY_IGNORE_GLOBAL_PHONE_NUMBER_FORMAT_BOOL and + // P-Associated-Uri does not have global number, + // try to find phone number excluding '+' one more time. + phoneNumber = extractPhoneNumberFromAssociatedUris(uris, /*isGlobalFormat*/false); + if (phoneNumber == null) { + loge("extract phone number without '+' failed"); + return; + } + mSubscriptionManagerService.setNumberFromIms(subId, phoneNumber); + } else { + logd("extract phone number failed"); } } @@ -2570,24 +2584,40 @@ public class ImsPhone extends ImsPhoneBase { * <p>Associated URIs are public user identities, and phone number could be used: * see 3GPP TS 24.229 5.4.1.2 and 3GPP TS 23.003 13.4. This algotihm look for the * possible "global number" in E.164 format. + * <p>If true try finding phone number even if the P-Associated-Uri does not have global + * number format. */ - private static String extractPhoneNumberFromAssociatedUris(Uri[] uris) { + private static String extractPhoneNumberFromAssociatedUris(Uri[] uris, boolean isGlobalFormat) { if (uris == null) { return null; } - return Arrays.stream(uris) + + Stream<String> intermediate = Arrays.stream(uris) // Phone number is an opaque URI "tel:<phone-number>" or "sip:<phone-number>@<...>" .filter(u -> u != null && u.isOpaque()) .filter(u -> "tel".equalsIgnoreCase(u.getScheme()) || "sip".equalsIgnoreCase(u.getScheme())) - .map(Uri::getSchemeSpecificPart) - // "Global number" should be in E.164 format starting with "+" e.g. "+447539447777" - .filter(ssp -> ssp != null && ssp.startsWith("+")) - // Remove whatever after "@" for sip URI - .map(ssp -> ssp.split("@")[0]) - // Returns the first winner - .findFirst() - .orElse(null); + .map(Uri::getSchemeSpecificPart); + + if (isGlobalFormat) { + // "Global number" should be in E.164 format starting with "+" e.g. "+447539447777" + return intermediate.filter(ssp -> ssp != null && ssp.startsWith("+")) + // Remove whatever after "@" for sip URI + .map(ssp -> ssp.split("@")[0]) + // Returns the first winner + .findFirst() + .orElse(null); + } else { + // non global number format + return intermediate.filter(ssp -> ssp != null) + // Remove whatever after "@" for sip URI + .map(ssp -> ssp.split("@")[0]) + // regular expression, allow only number + .filter(ssp -> ssp.matches("^[0-9]+$")) + // Returns the first winner + .findFirst() + .orElse(null); + } } public IccRecords getIccRecords() { @@ -2790,6 +2820,22 @@ public class ImsPhone extends ImsPhoneBase { pw.flush(); } + private boolean isAllowNonGlobalNumberFormat() { + PersistableBundle persistableBundle = null; + CarrierConfigManager carrierConfigManager = (CarrierConfigManager) mContext + .getSystemService(Context.CARRIER_CONFIG_SERVICE); + if (carrierConfigManager != null) { + persistableBundle = carrierConfigManager.getConfigForSubId(getSubId(), + CarrierConfigManager.Ims.KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL); + } + if (persistableBundle != null) { + return persistableBundle.getBoolean( + CarrierConfigManager.Ims.KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL, false); + } + + return false; + } + private void logi(String s) { Rlog.i(LOG_TAG, "[" + mPhoneId + "] " + s); } diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java index b984d84e9e..104c925759 100644..100755 --- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java @@ -82,7 +82,7 @@ public class ImsPhoneConnection extends Connection implements private ImsPhoneCall mParent; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private ImsCall mImsCall; - private Bundle mExtras = new Bundle(); + private final Bundle mExtras = new Bundle(); private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance(); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1491,7 +1491,10 @@ public class ImsPhoneConnection extends Connection implements * @return boolean: true if cross sim calling, false otherwise */ public boolean isCrossSimCall() { - return mImsCall != null && mImsCall.isCrossSimCall(); + if (mImsCall == null) { + return mExtras.getBoolean(ImsCallProfile.EXTRA_IS_CROSS_SIM_CALL); + } + return mImsCall.isCrossSimCall(); } /** diff --git a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java index cfa16d0591..afb87dd8ae 100644 --- a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java +++ b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java @@ -24,6 +24,7 @@ import android.telephony.Annotation.ApnType; import android.telephony.Annotation.DataFailureCause; import android.telephony.Annotation.NetworkType; import android.telephony.DataFailCause; +import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -319,7 +320,7 @@ public class DataCallSessionStats { ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); ServiceState serviceState = serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; - return serviceState != null && serviceState.getRoaming(); + return ServiceStateStats.isNetworkRoaming(serviceState, NetworkRegistrationInfo.DOMAIN_PS); } private boolean getIsOpportunistic() { diff --git a/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java b/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java new file mode 100644 index 0000000000..c7ef625a42 --- /dev/null +++ b/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java @@ -0,0 +1,189 @@ +/* + * 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 android.os.Handler; +import android.os.HandlerExecutor; +import android.os.HandlerThread; +import android.telephony.PreciseDataConnectionState; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback; +import android.telephony.TelephonyManager; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.Phone; + +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * This Tracker is monitoring precise data connection states for each APNs which are used for IMS + * calling such as IMS and Emergency APN. It uses a SparseArray to track each SIM's connection + * state. + * The tracker is started by {@link VoiceCallSessionStats} and update the states to + * VoiceCallSessionStats directly. + */ +public class DataConnectionStateTracker { + private static final SparseArray<DataConnectionStateTracker> sDataConnectionStateTracker = + new SparseArray<>(); + private final Executor mExecutor; + private Phone mPhone; + private int mSubId; + private HashMap<Integer, PreciseDataConnectionState> mLastPreciseDataConnectionState = + new HashMap<>(); + private PreciseDataConnectionStateListenerImpl mDataConnectionStateListener; + + private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener = + new SubscriptionManager.OnSubscriptionsChangedListener() { + @Override + public void onSubscriptionsChanged() { + if (mPhone == null) { + return; + } + int newSubId = mPhone.getSubId(); + if (mSubId == newSubId + || newSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + return; + } + + unregisterTelephonyListener(); + mSubId = newSubId; + registerTelephonyListener(mSubId); + } + }; + + private DataConnectionStateTracker() { + HandlerThread handlerThread = + new HandlerThread(DataConnectionStateTracker.class.getSimpleName()); + handlerThread.start(); + mExecutor = new HandlerExecutor(new Handler(handlerThread.getLooper())); + } + + /** Getting or Creating DataConnectionStateTracker based on phoneId */ + public static synchronized DataConnectionStateTracker getInstance(int phoneId) { + DataConnectionStateTracker dataConnectionStateTracker = + sDataConnectionStateTracker.get(phoneId); + if (dataConnectionStateTracker != null) { + return dataConnectionStateTracker; + } + + dataConnectionStateTracker = new DataConnectionStateTracker(); + sDataConnectionStateTracker.put(phoneId, dataConnectionStateTracker); + return dataConnectionStateTracker; + } + + /** Starting to monitor the precise data connection states */ + public void start(Phone phone) { + mPhone = phone; + mSubId = mPhone.getSubId(); + registerTelephonyListener(mSubId); + SubscriptionManager mSubscriptionManager = mPhone.getContext() + .getSystemService(SubscriptionManager.class); + if (mSubscriptionManager != null) { + mSubscriptionManager + .addOnSubscriptionsChangedListener(mExecutor, mSubscriptionsChangedListener); + } + } + + /** Stopping monitoring for the precise data connection states */ + public void stop() { + if (mPhone == null) { + return; + } + SubscriptionManager mSubscriptionManager = mPhone.getContext() + .getSystemService(SubscriptionManager.class); + if (mSubscriptionManager != null) { + mSubscriptionManager + .removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener); + } + unregisterTelephonyListener(); + mPhone = null; + mLastPreciseDataConnectionState.clear(); + } + + /** Returns data state of the last notified precise data connection state for apn type */ + public int getDataState(int apnType) { + if (!mLastPreciseDataConnectionState.containsKey(apnType)) { + return TelephonyManager.DATA_UNKNOWN; + } + return mLastPreciseDataConnectionState.get(apnType).getState(); + } + + private void registerTelephonyListener(int subId) { + if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + return; + } + TelephonyManager telephonyManager = + mPhone.getContext().getSystemService(TelephonyManager.class); + if (telephonyManager != null) { + mDataConnectionStateListener = new PreciseDataConnectionStateListenerImpl(mExecutor); + mDataConnectionStateListener.register(telephonyManager.createForSubscriptionId(subId)); + } + } + + private void unregisterTelephonyListener() { + if (mDataConnectionStateListener != null) { + mDataConnectionStateListener.unregister(); + mDataConnectionStateListener = null; + } + } + + @VisibleForTesting + public void notifyDataConnectionStateChanged(PreciseDataConnectionState connectionState) { + List<Integer> apnTypes = connectionState.getApnSetting().getApnTypes(); + if (apnTypes != null) { + for (int apnType : apnTypes) { + mLastPreciseDataConnectionState.put(apnType, connectionState); + } + } + + mPhone.getVoiceCallSessionStats().onPreciseDataConnectionStateChanged(connectionState); + } + + private class PreciseDataConnectionStateListenerImpl extends TelephonyCallback + implements TelephonyCallback.PreciseDataConnectionStateListener { + private final Executor mExecutor; + private TelephonyManager mTelephonyManager = null; + + PreciseDataConnectionStateListenerImpl(Executor executor) { + mExecutor = executor; + } + + public void register(TelephonyManager tm) { + if (tm == null) { + return; + } + mTelephonyManager = tm; + mTelephonyManager.registerTelephonyCallback(mExecutor, this); + } + + public void unregister() { + if (mTelephonyManager != null) { + mTelephonyManager.unregisterTelephonyCallback(this); + mTelephonyManager = null; + } + } + + @Override + public void onPreciseDataConnectionStateChanged( + PreciseDataConnectionState connectionState) { + notifyDataConnectionStateChanged(connectionState); + } + } +} diff --git a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java index 2f22196f45..0fd97ba25e 100644 --- a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java +++ b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java @@ -16,138 +16,376 @@ package com.android.internal.telephony.metrics; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.NetworkCapabilities; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; import android.telephony.AccessNetworkConstants; import android.telephony.Annotation.NetworkType; import android.telephony.CellSignalStrength; import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; +import android.telephony.data.DataCallResponse; +import android.telephony.data.DataCallResponse.LinkStatus; import com.android.internal.telephony.Phone; -import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; -import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.TelephonyStatsLog; +import com.android.internal.telephony.data.DataNetwork; +import com.android.internal.telephony.data.DataNetworkController; +import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback; import com.android.internal.telephony.data.DataStallRecoveryManager; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; +import com.android.telephony.Rlog; -/** Generates metrics related to data stall recovery events per phone ID for the pushed atom. */ +import java.util.List; + +/** + * Generates metrics related to data stall recovery events per phone ID for the pushed atom. + */ public class DataStallRecoveryStats { + /** - * Create and push new atom when there is a data stall recovery event - * - * @param recoveryAction Data stall recovery action - * @param phone + * Value indicating that link bandwidth is unspecified. + * Copied from {@code NetworkCapabilities#LINK_BANDWIDTH_UNSPECIFIED} */ + private static final int LINK_BANDWIDTH_UNSPECIFIED = 0; + + private static final String TAG = "DSRS-"; + + // Handler to upload metrics. + private final @NonNull Handler mHandler; + + private final @NonNull String mTag; + private final @NonNull Phone mPhone; + + // The interface name of the internet network. + private @Nullable String mIfaceName = null; + + /* Metrics and stats data variables */ + private int mPhoneId = 0; + private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; + private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + private int mBand = 0; + // The RAT used for data (including IWLAN). + private @NetworkType int mRat = TelephonyManager.NETWORK_TYPE_UNKNOWN; + private boolean mIsOpportunistic = false; + private boolean mIsMultiSim = false; + private int mNetworkRegState = NetworkRegistrationInfo + .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; + // Info of the other device in case of DSDS + private int mOtherSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + private int mOtherNetworkRegState = NetworkRegistrationInfo + .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; + // Link status of the data network + private @LinkStatus int mInternetLinkStatus = DataCallResponse.LINK_STATUS_UNKNOWN; + + // The link bandwidth of the data network + private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; + private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; - /* Since the Enum has been extended in Android T, we are mapping it to the correct number. */ - private static final int RECOVERY_ACTION_RADIO_RESTART_MAPPING = 3; - private static final int RECOVERY_ACTION_RESET_MODEM_MAPPING = 4; + /** + * Constructs a new instance of {@link DataStallRecoveryStats}. + */ + public DataStallRecoveryStats(@NonNull final Phone phone, + @NonNull final DataNetworkController dataNetworkController) { + mTag = TAG + phone.getPhoneId(); + mPhone = phone; + + HandlerThread handlerThread = new HandlerThread(mTag + "-thread"); + handlerThread.start(); + mHandler = new Handler(handlerThread.getLooper()); + + dataNetworkController.registerDataNetworkControllerCallback( + new DataNetworkControllerCallback(mHandler::post) { + @Override + public void onInternetDataNetworkConnected( + @NonNull List<DataNetwork> internetNetworks) { + for (DataNetwork dataNetwork : internetNetworks) { + mIfaceName = dataNetwork.getLinkProperties().getInterfaceName(); + break; + } + } + + @Override + public void onInternetDataNetworkDisconnected() { + mIfaceName = null; + } + + @Override + public void onPhysicalLinkStatusChanged(@LinkStatus int status) { + mInternetLinkStatus = status; + } + }); + } /** - * Called when data stall happened. + * Create and push new atom when there is a data stall recovery event. * - * @param recoveryAction The recovery action. - * @param phone The phone instance. - * @param isRecovered The data stall symptom recovered or not. - * @param durationMillis The duration from data stall symptom occurred. - * @param reason The recovered(data resume) reason. - * @param isFirstValidation The validation status if it's the first come after recovery. + * @param action The recovery action. + * @param isRecovered Whether the data stall has been recovered. + * @param duration The duration from data stall occurred in milliseconds. + * @param reason The reason for the recovery. + * @param isFirstValidation Whether this is the first validation after recovery. + * @param durationOfAction The duration of the current action in milliseconds. */ - public static void onDataStallEvent( - @DataStallRecoveryManager.RecoveryAction int recoveryAction, - Phone phone, + public void uploadMetrics( + @DataStallRecoveryManager.RecoveryAction int action, boolean isRecovered, - int durationMillis, + int duration, @DataStallRecoveryManager.RecoveredReason int reason, boolean isFirstValidation, - int durationMillisOfCurrentAction) { - if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { - phone = phone.getDefaultPhone(); + int durationOfAction) { + + mHandler.post(() -> { + // Update data stall stats + log("Set recovery action to " + action); + + // Refreshes the metrics data. + try { + refreshMetricsData(); + } catch (Exception e) { + loge("The metrics data cannot be refreshed.", e); + return; + } + + TelephonyStatsLog.write( + TelephonyStatsLog.DATA_STALL_RECOVERY_REPORTED, + mCarrierId, + mRat, + mSignalStrength, + action, + mIsOpportunistic, + mIsMultiSim, + mBand, + isRecovered, + duration, + reason, + mOtherSignalStrength, + mOtherNetworkRegState, + mNetworkRegState, + isFirstValidation, + mPhoneId, + durationOfAction, + mInternetLinkStatus, + mLinkUpBandwidthKbps, + mLinkDownBandwidthKbps); + + log("Upload stats: " + + "Action:" + + action + + ", Recovered:" + + isRecovered + + ", Duration:" + + duration + + ", Reason:" + + reason + + ", First validation:" + + isFirstValidation + + ", Duration of action:" + + durationOfAction + + ", " + + this); + }); + } + + /** + * Refreshes the metrics data. + */ + private void refreshMetricsData() { + logd("Refreshes the metrics data."); + // Update phone id/carrier id and signal strength + mPhoneId = mPhone.getPhoneId() + 1; + mCarrierId = mPhone.getCarrierId(); + mSignalStrength = mPhone.getSignalStrength().getLevel(); + + // Update the bandwidth. + updateBandwidths(); + + // Update the RAT and band. + updateRatAndBand(); + + // Update the opportunistic state. + mIsOpportunistic = getIsOpportunistic(mPhone); + + // Update the multi-SIM state. + mIsMultiSim = SimSlotState.getCurrentState().numActiveSims > 1; + + // Update the network registration state. + updateNetworkRegState(); + + // Update the DSDS information. + updateDsdsInfo(); + } + + /** + * Updates the bandwidth for the current data network. + */ + private void updateBandwidths() { + mLinkDownBandwidthKbps = mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; + + if (mIfaceName == null) { + loge("Interface name is null"); + return; + } + + DataNetworkController dataNetworkController = mPhone.getDataNetworkController(); + if (dataNetworkController == null) { + loge("DataNetworkController is null"); + return; + } + + DataNetwork dataNetwork = dataNetworkController.getDataNetworkByInterface(mIfaceName); + if (dataNetwork == null) { + loge("DataNetwork is null"); + return; + } + NetworkCapabilities networkCapabilities = dataNetwork.getNetworkCapabilities(); + if (networkCapabilities == null) { + loge("NetworkCapabilities is null"); + return; } - int carrierId = phone.getCarrierId(); - int rat = getRat(phone); - int band = - (rat == TelephonyManager.NETWORK_TYPE_IWLAN) ? 0 : ServiceStateStats.getBand(phone); - // the number returned here matches the SignalStrength enum we have - int signalStrength = phone.getSignalStrength().getLevel(); - boolean isOpportunistic = getIsOpportunistic(phone); - boolean isMultiSim = SimSlotState.getCurrentState().numActiveSims > 1; - - if (recoveryAction == DataStallRecoveryManager.RECOVERY_ACTION_RADIO_RESTART) { - recoveryAction = RECOVERY_ACTION_RADIO_RESTART_MAPPING; - } else if (recoveryAction == DataStallRecoveryManager.RECOVERY_ACTION_RESET_MODEM) { - recoveryAction = RECOVERY_ACTION_RESET_MODEM_MAPPING; + mLinkDownBandwidthKbps = networkCapabilities.getLinkDownstreamBandwidthKbps(); + mLinkUpBandwidthKbps = networkCapabilities.getLinkUpstreamBandwidthKbps(); + } + + private void updateRatAndBand() { + mRat = TelephonyManager.NETWORK_TYPE_UNKNOWN; + mBand = 0; + ServiceState serviceState = mPhone.getServiceState(); + if (serviceState == null) { + loge("ServiceState is null"); + return; + } + + mRat = serviceState.getDataNetworkType(); + mBand = + (mRat == TelephonyManager.NETWORK_TYPE_IWLAN) ? 0 : ServiceStateStats.getBand(mPhone); + } + + private static boolean getIsOpportunistic(@NonNull Phone phone) { + SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() + .getSubscriptionInfoInternal(phone.getSubId()); + return subInfo != null && subInfo.isOpportunistic(); + } + + private void updateNetworkRegState() { + mNetworkRegState = NetworkRegistrationInfo + .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; + + NetworkRegistrationInfo phoneRegInfo = mPhone.getServiceState() + .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + if (phoneRegInfo != null) { + mNetworkRegState = phoneRegInfo.getRegistrationState(); } + } - // collect info of the other device in case of DSDS - int otherSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - // the number returned here matches the NetworkRegistrationState enum we have - int otherNetworkRegState = NetworkRegistrationInfo - .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; + private void updateDsdsInfo() { + mOtherSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + mOtherNetworkRegState = NetworkRegistrationInfo + .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; for (Phone otherPhone : PhoneFactory.getPhones()) { - if (otherPhone.getPhoneId() == phone.getPhoneId()) continue; + if (otherPhone.getPhoneId() == mPhone.getPhoneId()) continue; if (!getIsOpportunistic(otherPhone)) { - otherSignalStrength = otherPhone.getSignalStrength().getLevel(); + mOtherSignalStrength = otherPhone.getSignalStrength().getLevel(); NetworkRegistrationInfo regInfo = otherPhone.getServiceState() .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS, - AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); if (regInfo != null) { - otherNetworkRegState = regInfo.getRegistrationState(); + mOtherNetworkRegState = regInfo.getRegistrationState(); } break; } } + } - // the number returned here matches the NetworkRegistrationState enum we have - int phoneNetworkRegState = NetworkRegistrationInfo - .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING; + /** + * Return bundled data stall recovery metrics data. + * + * @param action The recovery action. + * @param isRecovered Whether the data stall has been recovered. + * @param duration The duration from data stall occurred in milliseconds. + * @param reason The reason for the recovery. + * @param isFirstValidation Whether this is the first validation after recovery. + * @param durationOfAction The duration of the current action in milliseconds. + */ + public Bundle getDataStallRecoveryMetricsData( + @DataStallRecoveryManager.RecoveryAction int action, + boolean isRecovered, + int duration, + @DataStallRecoveryManager.RecoveredReason int reason, + boolean isFirstValidation, + int durationOfAction) { + Bundle bundle = new Bundle(); + bundle.putInt("Action", action); + bundle.putBoolean("IsRecovered", isRecovered); + bundle.putInt("Duration", duration); + bundle.putInt("Reason", reason); + bundle.putBoolean("IsFirstValidation", isFirstValidation); + bundle.putInt("DurationOfAction", durationOfAction); + bundle.putInt("PhoneId", mPhoneId); + bundle.putInt("CarrierId", mCarrierId); + bundle.putInt("SignalStrength", mSignalStrength); + bundle.putInt("Band", mBand); + bundle.putInt("Rat", mRat); + bundle.putBoolean("IsOpportunistic", mIsOpportunistic); + bundle.putBoolean("IsMultiSim", mIsMultiSim); + bundle.putInt("NetworkRegState", mNetworkRegState); + bundle.putInt("OtherSignalStrength", mOtherSignalStrength); + bundle.putInt("OtherNetworkRegState", mOtherNetworkRegState); + bundle.putInt("InternetLinkStatus", mInternetLinkStatus); + bundle.putInt("LinkDownBandwidthKbps", mLinkDownBandwidthKbps); + bundle.putInt("LinkUpBandwidthKbps", mLinkUpBandwidthKbps); + return bundle; + } - NetworkRegistrationInfo phoneRegInfo = phone.getServiceState() - .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS, - AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - if (phoneRegInfo != null) { - phoneNetworkRegState = phoneRegInfo.getRegistrationState(); - } + private void log(@NonNull String s) { + Rlog.i(mTag, s); + } - // reserve 0 for default value - int phoneId = phone.getPhoneId() + 1; - - TelephonyStatsLog.write( - TelephonyStatsLog.DATA_STALL_RECOVERY_REPORTED, - carrierId, - rat, - signalStrength, - recoveryAction, - isOpportunistic, - isMultiSim, - band, - isRecovered, - durationMillis, - reason, - otherSignalStrength, - otherNetworkRegState, - phoneNetworkRegState, - isFirstValidation, - phoneId, - durationMillisOfCurrentAction); + private void logd(@NonNull String s) { + Rlog.d(mTag, s); } - /** Returns the RAT used for data (including IWLAN). */ - private static @NetworkType int getRat(Phone phone) { - ServiceStateTracker serviceStateTracker = phone.getServiceStateTracker(); - ServiceState serviceState = - serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; - return serviceState != null - ? serviceState.getDataNetworkType() - : TelephonyManager.NETWORK_TYPE_UNKNOWN; + private void loge(@NonNull String s) { + Rlog.e(mTag, s); } - private static boolean getIsOpportunistic(Phone phone) { - SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() - .getSubscriptionInfoInternal(phone.getSubId()); - return subInfo != null && subInfo.isOpportunistic(); + private void loge(@NonNull String s, Throwable tr) { + Rlog.e(mTag, s, tr); + } + + @Override + public String toString() { + return "DataStallRecoveryStats {" + + "Phone id:" + + mPhoneId + + ", Signal strength:" + + mSignalStrength + + ", Band:" + mBand + + ", RAT:" + mRat + + ", Opportunistic:" + + mIsOpportunistic + + ", Multi-SIM:" + + mIsMultiSim + + ", Network reg state:" + + mNetworkRegState + + ", Other signal strength:" + + mOtherSignalStrength + + ", Other network reg state:" + + mOtherNetworkRegState + + ", Link status:" + + mInternetLinkStatus + + ", Link down bandwidth:" + + mLinkDownBandwidthKbps + + ", Link up bandwidth:" + + mLinkUpBandwidthKbps + + "}"; } } diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java index 5e00987911..3e49139cce 100644 --- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java +++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java @@ -923,7 +923,9 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { roundAndConvertMillisToSeconds(state.totalTimeMillis), state.isEmergencyOnly, state.isInternetPdnUp, - state.foldState); + state.foldState, + state.overrideVoiceService, + state.isDataEnabled); } private static StatsEvent buildStatsEvent(VoiceCallRatUsage usage) { @@ -975,7 +977,9 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback { session.isMultiparty, session.callDuration, session.lastKnownRat, - session.foldState); + session.foldState, + session.ratSwitchCountAfterConnected, + session.handoverInProgress); } private static StatsEvent buildStatsEvent(IncomingSms sms) { diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java index 5a21bafd49..d495ca28c5 100644 --- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java +++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java @@ -1709,7 +1709,9 @@ public class PersistAtomsStorage { && state.carrierId == key.carrierId && state.isEmergencyOnly == key.isEmergencyOnly && state.isInternetPdnUp == key.isInternetPdnUp - && state.foldState == key.foldState) { + && state.foldState == key.foldState + && state.overrideVoiceService == key.overrideVoiceService + && state.isDataEnabled == key.isDataEnabled) { return state; } } diff --git a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java index b830cd00d2..b6563dd4db 100644 --- a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java +++ b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java @@ -15,6 +15,7 @@ */ package com.android.internal.telephony.metrics; + import static android.telephony.TelephonyManager.DATA_CONNECTED; import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS; @@ -29,6 +30,7 @@ import android.telephony.AccessNetworkUtils; import android.telephony.Annotation.NetworkType; import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; +import android.telephony.ServiceState.RoamingType; import android.telephony.TelephonyManager; import com.android.internal.annotations.VisibleForTesting; @@ -44,6 +46,7 @@ import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceStat import com.android.telephony.Rlog; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; /** Tracks service state duration and switch metrics for each phone. */ @@ -52,6 +55,7 @@ public class ServiceStateStats extends DataNetworkControllerCallback { private final AtomicReference<TimestampedServiceState> mLastState = new AtomicReference<>(new TimestampedServiceState(null, 0L)); + private final AtomicBoolean mOverrideVoiceService = new AtomicBoolean(false); private final Phone mPhone; private final PersistAtomsStorage mStorage; private final DeviceStateHelper mDeviceStateHelper; @@ -114,8 +118,10 @@ public class ServiceStateStats extends DataNetworkControllerCallback { CellularServiceState newState = new CellularServiceState(); newState.voiceRat = getVoiceRat(mPhone, serviceState); newState.dataRat = getRat(serviceState, NetworkRegistrationInfo.DOMAIN_PS); - newState.voiceRoamingType = serviceState.getVoiceRoamingType(); - newState.dataRoamingType = serviceState.getDataRoamingType(); + newState.voiceRoamingType = + getNetworkRoamingState(serviceState, NetworkRegistrationInfo.DOMAIN_CS); + newState.dataRoamingType = + getNetworkRoamingState(serviceState, NetworkRegistrationInfo.DOMAIN_PS); newState.isEndc = isEndc(serviceState); newState.simSlotIndex = mPhone.getPhoneId(); newState.isMultiSim = SimSlotState.isMultiSim(); @@ -123,6 +129,8 @@ public class ServiceStateStats extends DataNetworkControllerCallback { newState.isEmergencyOnly = isEmergencyOnly(serviceState); newState.isInternetPdnUp = isInternetPdnUp(mPhone); newState.foldState = mDeviceStateHelper.getFoldState(); + newState.overrideVoiceService = mOverrideVoiceService.get(); + newState.isDataEnabled = mPhone.getDataSettingsManager().isDataEnabled(); TimestampedServiceState prevState = mLastState.getAndSet(new TimestampedServiceState(newState, now)); addServiceStateAndSwitch( @@ -150,6 +158,26 @@ public class ServiceStateStats extends DataNetworkControllerCallback { } } + /** Updates override state for voice service state when voice calling capability changes */ + public void onVoiceServiceStateOverrideChanged(boolean override) { + if (override == mOverrideVoiceService.get()) { + return; + } + mOverrideVoiceService.set(override); + final long now = getTimeMillis(); + TimestampedServiceState lastState = + mLastState.getAndUpdate( + state -> { + if (state.mServiceState == null) { + return new TimestampedServiceState(null, now); + } + CellularServiceState newServiceState = copyOf(state.mServiceState); + newServiceState.overrideVoiceService = mOverrideVoiceService.get(); + return new TimestampedServiceState(newServiceState, now); + }); + addServiceState(lastState, now); + } + private void addServiceState(TimestampedServiceState prevState, long now) { addServiceStateAndSwitch(prevState, now, null); } @@ -271,6 +299,8 @@ public class ServiceStateStats extends DataNetworkControllerCallback { copy.isEmergencyOnly = state.isEmergencyOnly; copy.isInternetPdnUp = state.isInternetPdnUp; copy.foldState = state.foldState; + copy.overrideVoiceService = state.overrideVoiceService; + copy.isDataEnabled = state.isDataEnabled; return copy; } @@ -380,6 +410,39 @@ public class ServiceStateStats extends DataNetworkControllerCallback { addServiceState(lastState, now); } + private static @RoamingType int getNetworkRoamingState( + ServiceState ss, @NetworkRegistrationInfo.Domain int domain) { + final NetworkRegistrationInfo nri = + ss.getNetworkRegistrationInfo(domain, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + if (nri == null) { + // No registration for domain + return ServiceState.ROAMING_TYPE_NOT_ROAMING; + } + @RoamingType int roamingType = nri.getRoamingType(); + if (nri.isNetworkRoaming() && roamingType == ServiceState.ROAMING_TYPE_NOT_ROAMING) { + // Roaming is overridden, exact roaming type unknown. + return ServiceState.ROAMING_TYPE_UNKNOWN; + } + return roamingType; + } + + /** Determines whether device is roaming, bypassing carrier overrides. */ + public static boolean isNetworkRoaming( + ServiceState ss, @NetworkRegistrationInfo.Domain int domain) { + if (ss == null) { + return false; + } + final NetworkRegistrationInfo nri = + ss.getNetworkRegistrationInfo(domain, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + return nri != null && nri.isNetworkRoaming(); + } + + /** Determines whether device is roaming in any domain, bypassing carrier overrides. */ + public static boolean isNetworkRoaming(ServiceState ss) { + return isNetworkRoaming(ss, NetworkRegistrationInfo.DOMAIN_CS) + || isNetworkRoaming(ss, NetworkRegistrationInfo.DOMAIN_PS); + } + @VisibleForTesting protected long getTimeMillis() { return SystemClock.elapsedRealtime(); diff --git a/src/java/com/android/internal/telephony/metrics/SmsStats.java b/src/java/com/android/internal/telephony/metrics/SmsStats.java index 2f1e6a7182..949b72e57b 100644 --- a/src/java/com/android/internal/telephony/metrics/SmsStats.java +++ b/src/java/com/android/internal/telephony/metrics/SmsStats.java @@ -386,7 +386,7 @@ public class SmsStats { private boolean getIsRoaming() { ServiceState serviceState = getServiceState(); - return serviceState != null ? serviceState.getRoaming() : false; + return ServiceStateStats.isNetworkRoaming(serviceState); } private int getCarrierId() { diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java index ba07fa035a..5d8e027e09 100644 --- a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java +++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java @@ -47,8 +47,10 @@ import android.telephony.Annotation.NetworkType; import android.telephony.AnomalyReporter; import android.telephony.DisconnectCause; import android.telephony.NetworkRegistrationInfo; +import android.telephony.PreciseDataConnectionState; import android.telephony.ServiceState; import android.telephony.TelephonyManager; +import android.telephony.data.ApnSetting; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsStreamMediaProfile; import android.util.LongSparseArray; @@ -163,6 +165,8 @@ public class VoiceCallSessionStats { public VoiceCallSessionStats(int phoneId, Phone phone) { mPhoneId = phoneId; mPhone = phone; + + DataConnectionStateTracker.getInstance(phoneId).start(phone); } /* CS calls */ @@ -395,6 +399,14 @@ public class VoiceCallSessionStats { } } + /** Updates internal states when IMS/Emergency PDN/PDU state changes */ + public synchronized void onPreciseDataConnectionStateChanged( + PreciseDataConnectionState connectionState) { + if (hasCalls()) { + updateVoiceCallSessionBearerState(connectionState); + } + } + /* internal */ /** Handles ringing MT call getting accepted. */ @@ -426,6 +438,7 @@ public class VoiceCallSessionStats { int bearer = getBearer(conn); ServiceState serviceState = getServiceState(); @NetworkType int rat = getVoiceRatWithVoNRFix(mPhone, serviceState, bearer); + @VideoState int videoState = conn.getVideoState(); VoiceCallSession proto = new VoiceCallSession(); proto.bearerAtStart = bearer; @@ -439,6 +452,7 @@ public class VoiceCallSessionStats { proto.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN; proto.ratAtEnd = rat; proto.ratSwitchCount = 0L; + proto.ratSwitchCountAfterConnected = 0L; proto.codecBitmask = 0L; proto.simSlotIndex = mPhoneId; proto.isMultiSim = SimSlotState.isMultiSim(); @@ -449,9 +463,11 @@ public class VoiceCallSessionStats { proto.srvccCancellationCount = 0L; proto.rttEnabled = false; proto.isEmergency = conn.isEmergencyCall() || conn.isNetworkIdentifiedEmergencyCall(); - proto.isRoaming = serviceState != null ? serviceState.getVoiceRoaming() : false; + proto.isRoaming = ServiceStateStats.isNetworkRoaming(serviceState); proto.isMultiparty = conn.isMultiparty(); proto.lastKnownRat = rat; + proto.videoEnabled = videoState != VideoProfile.STATE_AUDIO_ONLY ? true : false; + proto.handoverInProgress = isHandoverInProgress(bearer, proto.isEmergency); // internal fields for tracking if (getDirection(conn) == VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT) { @@ -607,6 +623,9 @@ public class VoiceCallSessionStats { private void updateRatAtEnd(VoiceCallSession proto, @NetworkType int rat) { if (proto.ratAtEnd != rat) { proto.ratSwitchCount++; + if (!proto.setupFailed) { + proto.ratSwitchCountAfterConnected++; + } proto.ratAtEnd = rat; if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) { proto.lastKnownRat = rat; @@ -680,6 +699,17 @@ public class VoiceCallSessionStats { return mPhone.getSignalStrength().getLevel(); } + private boolean isHandoverInProgress(int bearer, boolean isEmergency) { + // If the call is not IMS, the bearer will not be able to handover + if (bearer != VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) { + return false; + } + + int apnType = isEmergency ? ApnSetting.TYPE_EMERGENCY : ApnSetting.TYPE_IMS; + int dataState = DataConnectionStateTracker.getInstance(mPhoneId).getDataState(apnType); + return dataState == TelephonyManager.DATA_HANDOVER_IN_PROGRESS; + } + /** * This is a copy of ServiceStateStats.getVoiceRat(Phone, ServiceState, int) with minimum fix * required for tracking EPSFB correctly. @@ -922,4 +952,40 @@ public class VoiceCallSessionStats { return map; } + + private void updateVoiceCallSessionBearerState(PreciseDataConnectionState connectionState) { + ApnSetting apnSetting = connectionState.getApnSetting(); + if (apnSetting == null) { + return; + } + + int apnTypes = apnSetting.getApnTypeBitmask(); + if ((apnTypes & ApnSetting.TYPE_IMS) == 0 + && (apnTypes & ApnSetting.TYPE_EMERGENCY) == 0) { + return; + } + + for (int i = 0; i < mCallProtos.size(); i++) { + VoiceCallSession proto = mCallProtos.valueAt(i); + if (proto.bearerAtEnd == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) { + if (!proto.isEmergency && (apnTypes & ApnSetting.TYPE_IMS) != 0) { + updateHandoverState(proto, connectionState.getState()); + } + if (proto.isEmergency && (apnTypes & ApnSetting.TYPE_EMERGENCY) != 0) { + updateHandoverState(proto, connectionState.getState()); + } + } + } + } + + private void updateHandoverState(VoiceCallSession proto, int dataState) { + switch (dataState) { + case TelephonyManager.DATA_HANDOVER_IN_PROGRESS: + proto.handoverInProgress = true; + break; + default: + // All other states are considered as not in handover + proto.handoverInProgress = false; + } + } } diff --git a/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java b/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java new file mode 100644 index 0000000000..5d4e5dd57f --- /dev/null +++ b/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java @@ -0,0 +1,59 @@ +/* + * 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.satellite; + +import android.annotation.NonNull; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.Rlog; +import android.text.TextUtils; + +import java.util.List; + +/** + * This utility class is responsible for resolving NTN capabilities of a + * {@link NetworkRegistrationInfo}. + */ +public class NtnCapabilityResolver { + private static final String TAG = "NtnCapabilityResolver"; + + /** + * Resolve NTN capability by updating the input NetworkRegistrationInfo to indicate whether + * connecting to a non-terrestrial network and the available services supported by the network. + * + * @param networkRegistrationInfo The NetworkRegistrationInfo of a network. + * @param subId The subscription ID associated with a phone. + */ + public static void resolveNtnCapability( + @NonNull NetworkRegistrationInfo networkRegistrationInfo, int subId) { + SatelliteController satelliteController = SatelliteController.getInstance(); + List<String> satellitePlmnList = satelliteController.getSatellitePlmnList(); + String registeredPlmn = networkRegistrationInfo.getRegisteredPlmn(); + for (String satellitePlmn : satellitePlmnList) { + if (TextUtils.equals(satellitePlmn, registeredPlmn)) { + logd("Registered to satellite PLMN " + satellitePlmn); + networkRegistrationInfo.setIsNonTerrestrialNetwork(true); + networkRegistrationInfo.setAvailableServices( + satelliteController.getSupportedSatelliteServices(subId, satellitePlmn)); + break; + } + } + } + + private static void logd(@NonNull String log) { + Rlog.d(TAG, log); + } +} diff --git a/src/java/com/android/internal/telephony/satellite/PointingAppController.java b/src/java/com/android/internal/telephony/satellite/PointingAppController.java index f7f93cf441..9dba6f209c 100644 --- a/src/java/com/android/internal/telephony/satellite/PointingAppController.java +++ b/src/java/com/android/internal/telephony/satellite/PointingAppController.java @@ -37,6 +37,7 @@ import android.telephony.satellite.SatelliteManager; import android.text.TextUtils; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.Phone; import java.util.ArrayList; @@ -92,7 +93,8 @@ public class PointingAppController { * * @param context The Context for the PointingUIController. */ - private PointingAppController(@NonNull Context context) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public PointingAppController(@NonNull Context context) { mContext = context; mStartedSatelliteTransmissionUpdates = false; } @@ -102,11 +104,21 @@ public class PointingAppController { * transmission updates * @param startedSatelliteTransmissionUpdates boolean to set the flag */ + @VisibleForTesting public void setStartedSatelliteTransmissionUpdates( boolean startedSatelliteTransmissionUpdates) { mStartedSatelliteTransmissionUpdates = startedSatelliteTransmissionUpdates; } + /** + * Get the flag mStartedSatelliteTransmissionUpdates + * @return returns mStartedSatelliteTransmissionUpdates + */ + @VisibleForTesting + public boolean getStartedSatelliteTransmissionUpdates() { + return mStartedSatelliteTransmissionUpdates; + } + private static final class DatagramTransferStateHandlerRequest { public int datagramTransferState; public int pendingCount; @@ -128,6 +140,7 @@ public class PointingAppController { public static final int EVENT_DATAGRAM_TRANSFER_STATE_CHANGED = 4; private final ConcurrentHashMap<IBinder, ISatelliteTransmissionUpdateCallback> mListeners; + SatelliteTransmissionUpdateHandler(Looper looper) { super(looper); mListeners = new ConcurrentHashMap<>(); @@ -265,7 +278,6 @@ public class PointingAppController { mSatelliteTransmissionUpdateHandlers.get(subId); if (handler != null) { handler.removeListener(callback); - if (handler.hasListeners()) { result.accept(SatelliteManager.SATELLITE_ERROR_NONE); return; @@ -321,9 +333,11 @@ public class PointingAppController { /** * Stop receiving satellite transmission updates. + * Reset the flag mStartedSatelliteTransmissionUpdates * This can be called by the pointing UI when the user stops pointing to the satellite. */ public void stopSatelliteTransmissionUpdates(@NonNull Message message, @Nullable Phone phone) { + setStartedSatelliteTransmissionUpdates(false); if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) { SatelliteModemInterface.getInstance().stopSendingSatellitePointingInfo(message); return; diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java index 5cd84446c7..957b152c25 100644 --- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java +++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java @@ -16,6 +16,7 @@ package com.android.internal.telephony.satellite; +import android.annotation.ArrayRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; @@ -25,23 +26,28 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.res.Resources; import android.database.ContentObserver; import android.net.wifi.WifiManager; import android.nfc.NfcAdapter; -import android.nfc.NfcManager; import android.os.AsyncResult; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.ICancellationSignal; import android.os.Looper; import android.os.Message; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.SystemProperties; import android.provider.Settings; +import android.telephony.CarrierConfigManager; import android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -53,8 +59,10 @@ import android.telephony.satellite.SatelliteCapabilities; import android.telephony.satellite.SatelliteDatagram; import android.telephony.satellite.SatelliteManager; import android.util.Log; +import android.util.SparseArray; import android.uwb.UwbManager; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.CommandsInterface; @@ -63,10 +71,14 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats; import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats; import com.android.internal.telephony.satellite.metrics.SessionMetricsStats; +import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.util.FunctionalUtils; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; @@ -79,6 +91,8 @@ public class SatelliteController extends Handler { private static final String TAG = "SatelliteController"; /** Whether enabling verbose debugging message or not. */ private static final boolean DBG = false; + private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem"; + private static final boolean DEBUG = !"user".equals(Build.TYPE); /** File used to store shared preferences related to satellite. */ public static final String SATELLITE_SHARED_PREF = "satellite_shared_pref"; /** Value to pass for the setting key SATELLITE_MODE_ENABLED, enabled = 1, disabled = 0 */ @@ -153,6 +167,9 @@ public class SatelliteController extends Handler { @GuardedBy("mSatelliteEnabledRequestLock") private boolean mWaitingForRadioDisabled = false; + private boolean mWaitingForDisableSatelliteModemResponse = false; + private boolean mWaitingForSatelliteModemOff = false; + private final AtomicBoolean mRegisteredForProvisionStateChangedWithSatelliteService = new AtomicBoolean(false); private final AtomicBoolean mRegisteredForProvisionStateChangedWithPhone = @@ -193,6 +210,22 @@ public class SatelliteController extends Handler { private final Object mNeedsSatellitePointingLock = new Object(); @GuardedBy("mNeedsSatellitePointingLock") private boolean mNeedsSatellitePointing = false; + /** Key: subId, value: (key: PLMN, value: set of + * {@link android.telephony.NetworkRegistrationInfo.ServiceType}) + */ + @GuardedBy("mSupportedSatelliteServicesLock") + @NonNull private final Map<Integer, Map<String, Set<Integer>>> mSupportedSatelliteServices = + new HashMap<>(); + @NonNull private final Object mSupportedSatelliteServicesLock = new Object(); + /** Key: PLMN, value: set of {@link android.telephony.NetworkRegistrationInfo.ServiceType} */ + @NonNull private final Map<String, Set<Integer>> mSatelliteServicesSupportedByProviders; + @NonNull private final CarrierConfigManager mCarrierConfigManager; + @NonNull private final CarrierConfigManager.CarrierConfigChangeListener + mCarrierConfigChangeListener; + @NonNull private final Object mCarrierConfigArrayLock = new Object(); + @GuardedBy("mCarrierConfigArrayLock") + @NonNull private final SparseArray<PersistableBundle> mCarrierConfigArray = new SparseArray<>(); + @NonNull private final List<String> mSatellitePlmnList; /** * @return The singleton instance of SatelliteController. @@ -260,6 +293,7 @@ public class SatelliteController extends Handler { registerForPendingDatagramCount(); registerForSatelliteModemStateChanged(); mContentResolver = mContext.getContentResolver(); + mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class); try { mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF, @@ -281,6 +315,16 @@ public class SatelliteController extends Handler { Settings.Global.getUriFor(Settings.Global.SATELLITE_MODE_RADIOS), false, satelliteModeRadiosContentObserver); } + + mSatelliteServicesSupportedByProviders = readSupportedSatelliteServicesFromOverlayConfig(); + mSatellitePlmnList = + mSatelliteServicesSupportedByProviders.keySet().stream().toList(); + updateSupportedSatelliteServicesForActiveSubscriptions(); + mCarrierConfigChangeListener = + (slotIndex, subId, carrierId, specificCarrierId) -> + handleCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId); + mCarrierConfigManager.registerCarrierConfigChangeListener( + new HandlerExecutor(new Handler(looper)), mCarrierConfigChangeListener); } @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @@ -418,44 +462,50 @@ public class SatelliteController extends Handler { case BluetoothAdapter.ACTION_STATE_CHANGED: int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); - logd("Bluetooth state updated to " + btState); synchronized (mRadioStateLock) { + boolean currentBTStateEnabled = mBTStateEnabled; if (btState == BluetoothAdapter.STATE_OFF) { mBTStateEnabled = false; evaluateToSendSatelliteEnabledSuccess(); } else if (btState == BluetoothAdapter.STATE_ON) { mBTStateEnabled = true; } - logd("mBTStateEnabled: " + mBTStateEnabled); + if (currentBTStateEnabled != mBTStateEnabled) { + logd("mBTStateEnabled=" + mBTStateEnabled); + } } break; case NfcAdapter.ACTION_ADAPTER_STATE_CHANGED: int nfcState = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, -1); - logd("Nfc state updated to " + nfcState); synchronized (mRadioStateLock) { + boolean currentNfcStateEnabled = mNfcStateEnabled; if (nfcState == NfcAdapter.STATE_ON) { mNfcStateEnabled = true; } else if (nfcState == NfcAdapter.STATE_OFF) { mNfcStateEnabled = false; evaluateToSendSatelliteEnabledSuccess(); } - logd("mNfcStateEnabled: " + mNfcStateEnabled); + if (currentNfcStateEnabled != mNfcStateEnabled) { + logd("mNfcStateEnabled=" + mNfcStateEnabled); + } } break; case WifiManager.WIFI_STATE_CHANGED_ACTION: int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN); - logd("Wifi state updated to " + wifiState); synchronized (mRadioStateLock) { + boolean currentWifiStateEnabled = mWifiStateEnabled; if (wifiState == WifiManager.WIFI_STATE_ENABLED) { mWifiStateEnabled = true; } else if (wifiState == WifiManager.WIFI_STATE_DISABLED) { mWifiStateEnabled = false; evaluateToSendSatelliteEnabledSuccess(); } - logd("mWifiStateEnabled: " + mWifiStateEnabled); + if (currentWifiStateEnabled != mWifiStateEnabled) { + logd("mWifiStateEnabled=" + mWifiStateEnabled); + } } break; default: @@ -686,17 +736,17 @@ public class SatelliteController extends Handler { SatelliteManager.SATELLITE_ERROR_NONE); } } - resetSatelliteEnabledRequest(); - setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE); - setDemoModeEnabled(argument.enableDemoMode); synchronized (mIsSatelliteEnabledLock) { - mIsSatelliteEnabled = argument.enableSatellite; + if (!mWaitingForSatelliteModemOff) { + moveSatelliteToOffStateAndCleanUpResources( + SatelliteManager.SATELLITE_ERROR_NONE, argument.callback); + } else { + logd("Wait for satellite modem off before updating satellite" + + " modem state"); + } + mWaitingForDisableSatelliteModemResponse = false; } - // If satellite is disabled, send success to callback immediately - argument.callback.accept(error); - updateSatelliteEnabledState( - argument.enableSatellite, "EVENT_SET_SATELLITE_ENABLED_DONE"); } } else { synchronized (mSatelliteEnabledRequestLock) { @@ -728,6 +778,9 @@ public class SatelliteController extends Handler { .reportSessionMetrics(); } else { mControllerMetricsStats.onSatelliteDisabled(); + synchronized (mIsSatelliteEnabledLock) { + mWaitingForDisableSatelliteModemResponse = false; + } } break; } @@ -951,19 +1004,24 @@ public class SatelliteController extends Handler { || mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE) { mIsRadioOn = false; logd("Radio State Changed to " + mCi.getRadioState()); - IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() { - @Override - public void accept(int result) { - logd("RequestSatelliteEnabled: result=" + result); - } - }; - Phone phone = SatelliteServiceUtils.getPhone(); - Consumer<Integer> result = FunctionalUtils - .ignoreRemoteException(errorCallback::accept); - RequestSatelliteEnabledArgument message = - new RequestSatelliteEnabledArgument(false, false, result); - request = new SatelliteControllerHandlerRequest(message, phone); - handleSatelliteEnabled(request); + if (isSatelliteEnabled()) { + IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + logd("RequestSatelliteEnabled: result=" + result); + } + }; + Phone phone = SatelliteServiceUtils.getPhone(); + Consumer<Integer> result = FunctionalUtils + .ignoreRemoteException(errorCallback::accept); + RequestSatelliteEnabledArgument message = + new RequestSatelliteEnabledArgument(false, false, result); + request = new SatelliteControllerHandlerRequest(message, phone); + handleSatelliteEnabled(request); + } else { + logd("EVENT_RADIO_STATE_CHANGED: Satellite modem is currently disabled." + + " Ignored the event"); + } } else { mIsRadioOn = true; if (!mSatelliteModemInterface.isSatelliteServiceSupported()) { @@ -1155,13 +1213,13 @@ public class SatelliteController extends Handler { if (mSatelliteEnabledRequest == null) { mSatelliteEnabledRequest = request; } else if (mSatelliteEnabledRequest.enableSatellite == request.enableSatellite) { - logd("requestSatelliteEnabled enableSatellite: " + enableSatellite + logd("requestSatelliteEnabled enableSatellite: " + enableSatellite + " is already in progress."); result.accept(SatelliteManager.SATELLITE_REQUEST_IN_PROGRESS); return; } else if (mSatelliteEnabledRequest.enableSatellite == false && request.enableSatellite == true) { - logd("requestSatelliteEnabled enableSatellite: " + enableSatellite + " cannot be " + logd("requestSatelliteEnabled enableSatellite: " + enableSatellite + " cannot be " + "processed. Disable satellite is already in progress."); result.accept(SatelliteManager.SATELLITE_ERROR); return; @@ -1763,33 +1821,24 @@ public class SatelliteController extends Handler { * {@code false} otherwise. */ public boolean setSatelliteServicePackageName(@Nullable String servicePackageName) { - boolean result = mSatelliteModemInterface.setSatelliteServicePackageName( - servicePackageName); - if (result) { - logd("setSatelliteServicePackageName: Resetting cached states"); + if (!isMockModemAllowed()) return false; - // Cached states need to be cleared whenever switching satellite vendor services. - synchronized (mIsSatelliteSupportedLock) { - mIsSatelliteSupported = null; - } - synchronized (mIsSatelliteProvisionedLock) { - mIsSatelliteProvisioned = null; - } - synchronized (mIsSatelliteEnabledLock) { - mIsSatelliteEnabled = null; - } - synchronized (mSatelliteCapabilitiesLock) { - mSatelliteCapabilities = null; - } - ResultReceiver receiver = new ResultReceiver(this) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - logd("requestIsSatelliteSupported: resultCode=" + resultCode); - } - }; - requestIsSatelliteSupported(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, receiver); + // Cached states need to be cleared whenever switching satellite vendor services. + logd("setSatelliteServicePackageName: Resetting cached states"); + synchronized (mIsSatelliteSupportedLock) { + mIsSatelliteSupported = null; + } + synchronized (mIsSatelliteProvisionedLock) { + mIsSatelliteProvisioned = null; + } + synchronized (mIsSatelliteEnabledLock) { + mIsSatelliteEnabled = null; } - return result; + synchronized (mSatelliteCapabilitiesLock) { + mSatelliteCapabilities = null; + } + mSatelliteModemInterface.setSatelliteServicePackageName(servicePackageName); + return true; } /** @@ -1891,6 +1940,40 @@ public class SatelliteController extends Handler { } /** + * @return The list of satellite PLMNs used for connecting to satellite networks. + */ + @NonNull + public List<String> getSatellitePlmnList() { + return new ArrayList<>(mSatellitePlmnList); + } + + /** + * @param subId Subscription ID. + * @param plmn The satellite roaming plmn. + * @return The list of services supported by the carrier associated with the {@code subId} for + * the satellite network {@code plmn}. + */ + @NonNull + public List<Integer> getSupportedSatelliteServices(int subId, String plmn) { + synchronized (mSupportedSatelliteServicesLock) { + if (mSupportedSatelliteServices.containsKey(subId)) { + Map<String, Set<Integer>> supportedServices = + mSupportedSatelliteServices.get(subId); + if (supportedServices != null && supportedServices.containsKey(plmn)) { + return new ArrayList<>(supportedServices.get(plmn)); + } else { + loge("getSupportedSatelliteServices: subId=" + subId + ", supportedServices " + + "does not contain key plmn=" + plmn); + } + } else { + loge("getSupportedSatelliteServices: mSupportedSatelliteServices does contain key" + + " subId=" + subId); + } + return new ArrayList<>(); + } + } + + /** * If we have not successfully queried the satellite modem for its satellite service support, * we will retry the query one more time. Otherwise, we will return the cached result. */ @@ -2045,13 +2128,23 @@ public class SatelliteController extends Handler { private void handleSatelliteEnabled(SatelliteControllerHandlerRequest request) { RequestSatelliteEnabledArgument argument = (RequestSatelliteEnabledArgument) request.argument; + Phone phone = request.phone; + + if (!argument.enableSatellite && (mSatelliteModemInterface.isSatelliteServiceSupported() + || phone != null)) { + synchronized (mIsSatelliteEnabledLock) { + mWaitingForDisableSatelliteModemResponse = true; + mWaitingForSatelliteModemOff = true; + } + } + Message onCompleted = obtainMessage(EVENT_SET_SATELLITE_ENABLED_DONE, request); if (mSatelliteModemInterface.isSatelliteServiceSupported()) { mSatelliteModemInterface.requestSatelliteEnabled(argument.enableSatellite, argument.enableDemoMode, onCompleted); return; } - Phone phone = request.phone; + if (phone != null) { phone.setSatellitePower(onCompleted, argument.enableSatellite); } else { @@ -2190,15 +2283,31 @@ public class SatelliteController extends Handler { private void handleEventSatelliteModemStateChanged( @SatelliteManager.SatelliteModemState int state) { logd("handleEventSatelliteModemStateChanged: state=" + state); - if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF - || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) { - setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE); - setDemoModeEnabled(false); - updateSatelliteEnabledState( - false, "handleEventSatelliteModemStateChanged"); - cleanUpResources(state); + if (state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE + || state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) { + synchronized (mIsSatelliteEnabledLock) { + if ((state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) + || ((mIsSatelliteEnabled == null || isSatelliteEnabled()) + && !mWaitingForDisableSatelliteModemResponse)) { + int error = (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) + ? SatelliteManager.SATELLITE_ERROR_NONE + : SatelliteManager.SATELLITE_INVALID_MODEM_STATE; + Consumer<Integer> callback = null; + synchronized (mSatelliteEnabledRequestLock) { + if (mSatelliteEnabledRequest != null) { + callback = mSatelliteEnabledRequest.callback; + } + } + moveSatelliteToOffStateAndCleanUpResources(error, callback); + } else { + logd("Either waiting for the response of disabling satellite modem or the event" + + " should be ignored because isSatelliteEnabled=" + + isSatelliteEnabled() + + ", mIsSatelliteEnabled=" + mIsSatelliteEnabled); + } + mWaitingForSatelliteModemOff = false; + } } - mDatagramController.onSatelliteModemStateChanged(state); } @@ -2252,16 +2361,17 @@ public class SatelliteController extends Handler { } } - private void cleanUpResources(@SatelliteManager.SatelliteModemState int state) { - logd("cleanUpResources"); - if (state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) { - synchronized (mSatelliteEnabledRequestLock) { - if (mSatelliteEnabledRequest != null) { - mSatelliteEnabledRequest.callback.accept( - SatelliteManager.SATELLITE_INVALID_MODEM_STATE); - } - } + private void moveSatelliteToOffStateAndCleanUpResources( + @SatelliteManager.SatelliteError int error, @Nullable Consumer<Integer> callback) { + logd("moveSatelliteToOffStateAndCleanUpResources"); + synchronized (mIsSatelliteEnabledLock) { resetSatelliteEnabledRequest(); + setDemoModeEnabled(false); + mIsSatelliteEnabled = false; + setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE); + if (callback != null) callback.accept(error); + updateSatelliteEnabledState( + false, "moveSatelliteToOffStateAndCleanUpResources"); } } @@ -2270,6 +2380,100 @@ public class SatelliteController extends Handler { mDatagramController.setDemoMode(mIsDemoModeEnabled); } + private boolean isMockModemAllowed() { + return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false)); + } + + private void updateSupportedSatelliteServicesForActiveSubscriptions() { + synchronized (mSupportedSatelliteServicesLock) { + mSupportedSatelliteServices.clear(); + int[] activeSubIds = SubscriptionManagerService.getInstance().getActiveSubIdList(true); + if (activeSubIds != null) { + for (int subId : activeSubIds) { + updateSupportedSatelliteServices(subId); + } + } else { + loge("updateSupportedSatelliteServicesForActiveSubscriptions: " + + "activeSubIds is null"); + } + } + } + + private void updateSupportedSatelliteServices(int subId) { + Map<String, Set<Integer>> carrierSupportedSatelliteServicesPerPlmn = + readSupportedSatelliteServicesFromCarrierConfig(subId); + synchronized (mSupportedSatelliteServicesLock) { + mSupportedSatelliteServices.put(subId, + SatelliteServiceUtils.mergeSupportedSatelliteServices( + mSatelliteServicesSupportedByProviders, + carrierSupportedSatelliteServicesPerPlmn)); + } + } + + @NonNull + private Map<String, Set<Integer>> readSupportedSatelliteServicesFromOverlayConfig() { + String[] supportedServices = readStringArrayFromOverlayConfig( + R.array.config_satellite_services_supported_by_providers); + return SatelliteServiceUtils.parseSupportedSatelliteServices(supportedServices); + } + + @NonNull + private Map<String, Set<Integer>> readSupportedSatelliteServicesFromCarrierConfig(int subId) { + synchronized (mCarrierConfigArrayLock) { + PersistableBundle config = mCarrierConfigArray.get(subId); + if (config == null) { + config = getConfigForSubId(subId); + mCarrierConfigArray.put(subId, config); + } + return SatelliteServiceUtils.parseSupportedSatelliteServices( + config.getPersistableBundle(CarrierConfigManager + .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE)); + } + } + + @NonNull private PersistableBundle getConfigForSubId(int subId) { + PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId, + CarrierConfigManager + .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE); + if (config == null || config.isEmpty()) { + config = CarrierConfigManager.getDefaultConfig(); + } + return config; + } + + private void handleCarrierConfigChanged(int slotIndex, int subId, int carrierId, + int specificCarrierId) { + logd("handleCarrierConfigChanged(): slotIndex(" + slotIndex + "), subId(" + + subId + "), carrierId(" + carrierId + "), specificCarrierId(" + + specificCarrierId + ")"); + if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + return; + } + + updateCarrierConfig(subId); + updateSupportedSatelliteServicesForActiveSubscriptions(); + } + + private void updateCarrierConfig(int subId) { + synchronized (mCarrierConfigArrayLock) { + mCarrierConfigArray.put(subId, getConfigForSubId(subId)); + } + } + + @NonNull + private String[] readStringArrayFromOverlayConfig(@ArrayRes int id) { + String[] strArray = null; + try { + strArray = mContext.getResources().getStringArray(id); + } catch (Resources.NotFoundException ex) { + loge("readStringArrayFromOverlayConfig: id= " + id + ", ex=" + ex); + } + if (strArray == null) { + strArray = new String[0]; + } + return strArray; + } + private static void logd(@NonNull String log) { Rlog.d(TAG, log); } diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java index 80c67b315a..ebf7780a49 100644 --- a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java +++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java @@ -24,14 +24,12 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.AsyncResult; import android.os.Binder; -import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RegistrantList; import android.os.RemoteException; -import android.os.SystemProperties; import android.telephony.Rlog; import android.telephony.satellite.SatelliteCapabilities; import android.telephony.satellite.SatelliteDatagram; @@ -57,8 +55,6 @@ import java.util.Arrays; */ public class SatelliteModemInterface { private static final String TAG = "SatelliteModemInterface"; - private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem"; - private static final boolean DEBUG = !"user".equals(Build.TYPE); private static final long REBIND_INITIAL_DELAY = 2 * 1000; // 2 seconds private static final long REBIND_MAXIMUM_DELAY = 64 * 1000; // 1 minute private static final int REBIND_MULTIPLIER = 2; @@ -1016,13 +1012,7 @@ public class SatelliteModemInterface { * {@code false} otherwise. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public boolean setSatelliteServicePackageName(@Nullable String servicePackageName) { - if (!shouldAllowModifyingSatelliteServicePackageName()) { - loge("setSatelliteServicePackageName: modifying satellite service package name " - + "is not allowed"); - return false; - } - + public void setSatelliteServicePackageName(@Nullable String servicePackageName) { logd("setSatelliteServicePackageName: config_satellite_service_package is " + "updated, new packageName=" + servicePackageName); mExponentialBackoff.stop(); @@ -1042,8 +1032,6 @@ public class SatelliteModemInterface { mIsSatelliteServiceSupported = getSatelliteServiceSupport(); bindService(); mExponentialBackoff.start(); - - return true; } @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @@ -1055,10 +1043,6 @@ public class SatelliteModemInterface { message.sendToTarget(); } - private boolean shouldAllowModifyingSatelliteServicePackageName() { - return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false)); - } - private static void logd(@NonNull String log) { Rlog.d(TAG, log); } diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java index f11ca661de..151b69dd77 100644 --- a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java +++ b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java @@ -16,11 +16,16 @@ package com.android.internal.telephony.satellite; +import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE; +import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.os.AsyncResult; import android.os.Binder; +import android.os.PersistableBundle; +import android.telephony.NetworkRegistrationInfo; import android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.satellite.AntennaPosition; @@ -40,7 +45,9 @@ import com.android.internal.telephony.subscription.SubscriptionManagerService; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /** @@ -267,6 +274,127 @@ public class SatelliteServiceUtils { } /** + * Expected format of each input string in the array: "PLMN_1:service_1,service_2,..." + * + * @return The map of supported services with key: PLMN, value: set of services supported by + * the PLMN. + */ + @NonNull + @NetworkRegistrationInfo.ServiceType + public static Map<String, Set<Integer>> parseSupportedSatelliteServices( + String[] supportedSatelliteServicesStrArray) { + Map<String, Set<Integer>> supportedServicesMap = new HashMap<>(); + if (supportedSatelliteServicesStrArray == null + || supportedSatelliteServicesStrArray.length == 0) { + return supportedServicesMap; + } + + for (String supportedServicesPerPlmnStr : supportedSatelliteServicesStrArray) { + String[] pairOfPlmnAndsupportedServicesStr = + supportedServicesPerPlmnStr.split(":"); + if (pairOfPlmnAndsupportedServicesStr != null + && (pairOfPlmnAndsupportedServicesStr.length == 1 + || pairOfPlmnAndsupportedServicesStr.length == 2)) { + String plmn = pairOfPlmnAndsupportedServicesStr[0]; + Set<Integer> supportedServicesSet = new HashSet<>(); + if (pairOfPlmnAndsupportedServicesStr.length == 2) { + String[] supportedServicesStrArray = + pairOfPlmnAndsupportedServicesStr[1].split(","); + for (String service : supportedServicesStrArray) { + try { + int serviceType = Integer.parseInt(service); + if (isServiceTypeValid(serviceType)) { + supportedServicesSet.add(serviceType); + } else { + loge("parseSupportedSatelliteServices: invalid serviceType=" + + serviceType); + } + } catch (NumberFormatException e) { + loge("parseSupportedSatelliteServices: supportedServicesPerPlmnStr=" + + supportedServicesPerPlmnStr + ", service=" + service + + ", e=" + e); + } + } + } + supportedServicesMap.put(plmn, supportedServicesSet); + } else { + loge("parseSupportedSatelliteServices: invalid format input, " + + "supportedServicesPerPlmnStr=" + supportedServicesPerPlmnStr); + } + } + return supportedServicesMap; + } + + /** + * Expected format of the input dictionary bundle is: + * <ul> + * <li>Key: PLMN string.</li> + * <li>Value: A string with format "service_1,service_2,..."</li> + * </ul> + * @return The map of supported services with key: PLMN, value: set of services supported by + * the PLMN. + */ + @NonNull + @NetworkRegistrationInfo.ServiceType + public static Map<String, Set<Integer>> parseSupportedSatelliteServices( + PersistableBundle supportedServicesBundle) { + Map<String, Set<Integer>> supportedServicesMap = new HashMap<>(); + if (supportedServicesBundle == null || supportedServicesBundle.isEmpty()) { + return supportedServicesMap; + } + + for (String plmn : supportedServicesBundle.keySet()) { + Set<Integer> supportedServicesSet = new HashSet<>(); + for (int serviceType : supportedServicesBundle.getIntArray(plmn)) { + if (isServiceTypeValid(serviceType)) { + supportedServicesSet.add(serviceType); + } else { + loge("parseSupportedSatelliteServices: invalid service type=" + serviceType + + " for plmn=" + plmn); + } + } + supportedServicesMap.put(plmn, supportedServicesSet); + } + return supportedServicesMap; + } + + /** + * For the PLMN that exists in both {@code providerSupportedServices} and + * {@code carrierSupportedServices}, the supported services will be the intersection of the two + * sets. For the PLMN that is present in {@code providerSupportedServices} but not in + * {@code carrierSupportedServices}, the provider supported services will be used. The rest + * will not be used. + * + * @param providerSupportedServices Satellite provider supported satellite services. + * @param carrierSupportedServices Carrier supported satellite services. + * @return The supported satellite services by the device for the corresponding carrier and the + * satellite provider. + */ + @NonNull + @NetworkRegistrationInfo.ServiceType + public static Map<String, Set<Integer>> mergeSupportedSatelliteServices( + @NonNull @NetworkRegistrationInfo.ServiceType Map<String, Set<Integer>> + providerSupportedServices, + @NonNull @NetworkRegistrationInfo.ServiceType Map<String, Set<Integer>> + carrierSupportedServices) { + Map<String, Set<Integer>> supportedServicesMap = new HashMap<>(); + for (Map.Entry<String, Set<Integer>> entry : providerSupportedServices.entrySet()) { + Set<Integer> supportedServices = new HashSet<>(entry.getValue()); + if (carrierSupportedServices.containsKey(entry.getKey())) { + supportedServices.retainAll(carrierSupportedServices.get(entry.getKey())); + } + if (!supportedServices.isEmpty()) { + supportedServicesMap.put(entry.getKey(), supportedServices); + } + } + return supportedServicesMap; + } + + private static boolean isServiceTypeValid(int serviceType) { + return (serviceType >= FIRST_SERVICE_TYPE && serviceType <= LAST_SERVICE_TYPE); + } + + /** * Return phone associated with phoneId 0. * * @return phone associated with phoneId 0 or {@code null} if it doesn't exist. diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java index b90dc5edf5..124437cd6b 100644 --- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java +++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java @@ -2018,21 +2018,21 @@ public class SubscriptionDatabaseManager extends Handler { } /** - * Reload the database from content provider to the cache. + * Reload the database from content provider to the cache. This must be a synchronous operation + * to prevent cache/database out-of-sync. Callers should be cautious to call this method because + * it might take longer time to complete. */ - public void reloadDatabase() { - if (mAsyncMode) { - post(this::loadDatabaseInternal); - } else { - loadDatabaseInternal(); - } + public void reloadDatabaseSync() { + logl("reloadDatabaseSync"); + // Synchronously load the database into the cache. + loadDatabaseInternal(); } /** * Load the database from content provider to the cache. */ private void loadDatabaseInternal() { - log("loadDatabaseInternal"); + logl("loadDatabaseInternal"); try (Cursor cursor = mContext.getContentResolver().query( SimInfo.CONTENT_URI, null, null, null, null)) { mReadWriteLock.writeLock().lock(); diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java index a3377a277b..ba69d8a7c0 100644 --- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java +++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java @@ -1069,6 +1069,8 @@ public class SubscriptionManagerService extends ISub.Stub { int subId = insertSubscriptionInfo(embeddedProfile.getIccid(), SubscriptionManager.INVALID_SIM_SLOT_INDEX, null, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); + mSubscriptionDatabaseManager.setDisplayName(subId, mContext.getResources() + .getString(R.string.default_card_name, subId)); subInfo = mSubscriptionDatabaseManager.getSubscriptionInfoInternal(subId); } @@ -1116,6 +1118,7 @@ public class SubscriptionManagerService extends ISub.Stub { // CARD_ID field should not contain the EID if (cardId >= 0 && mUiccController.getCardIdForDefaultEuicc() != TelephonyManager.UNSUPPORTED_CARD_ID) { + builder.setCardId(cardId); builder.setCardString(mUiccController.convertToCardString(cardId)); } @@ -1345,6 +1348,8 @@ public class SubscriptionManagerService extends ISub.Stub { // This is a new SIM card. Insert a new record. subId = insertSubscriptionInfo(iccId, phoneId, null, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); + mSubscriptionDatabaseManager.setDisplayName(subId, + mContext.getResources().getString(R.string.default_card_name, subId)); } else { subId = subInfo.getSubscriptionId(); log("updateSubscription: Found existing subscription. subId= " + subId @@ -1421,12 +1426,15 @@ public class SubscriptionManagerService extends ISub.Stub { } // Attempt to restore SIM specific settings when SIM is loaded. - mContext.getContentResolver().call( + Bundle result = mContext.getContentResolver().call( SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI, SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME, iccId, null); - log("Reload the database."); - mSubscriptionDatabaseManager.reloadDatabase(); + if (result != null && result.getBoolean( + SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_DATABASE_UPDATED)) { + logl("Sim specific settings changed the database."); + mSubscriptionDatabaseManager.reloadDatabaseSync(); + } } log("updateSubscription: " + mSubscriptionDatabaseManager @@ -3562,10 +3570,10 @@ public class SubscriptionManagerService extends ISub.Stub { * * @param subId the unique SubscriptionInfo index in database * @return userHandle associated with this subscription - * or {@code null} if subscription is not associated with any user. + * or {@code null} if subscription is not associated with any user + * or {code null} if subscripiton is not available on the device. * * @throws SecurityException if doesn't have required permission. - * @throws IllegalArgumentException if {@code subId} is invalid. */ @Override @Nullable @@ -3578,8 +3586,7 @@ public class SubscriptionManagerService extends ISub.Stub { SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager .getSubscriptionInfoInternal(subId); if (subInfo == null) { - throw new IllegalArgumentException("getSubscriptionUserHandle: Invalid subId: " - + subId); + return null; } UserHandle userHandle = UserHandle.of(subInfo.getUserId()); @@ -3727,12 +3734,16 @@ public class SubscriptionManagerService extends ISub.Stub { Bundle bundle = new Bundle(); bundle.putByteArray(SubscriptionManager.KEY_SIM_SPECIFIC_SETTINGS_DATA, data); logl("restoreAllSimSpecificSettingsFromBackup"); - mContext.getContentResolver().call( + Bundle result = mContext.getContentResolver().call( SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI, SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME, null, bundle); - // After restoring, we need to reload the content provider into the cache. - mSubscriptionDatabaseManager.reloadDatabase(); + + if (result != null && result.getBoolean( + SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_DATABASE_UPDATED)) { + logl("Sim specific settings changed the database."); + mSubscriptionDatabaseManager.reloadDatabaseSync(); + } } finally { Binder.restoreCallingIdentity(token); } diff --git a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java index be045c4c92..680af46ccc 100644 --- a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java +++ b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java @@ -106,14 +106,21 @@ public class UiccPkcs15 extends Handler { mCallback.sendToTarget(); return; } - + IccIoResult response; switch (msg.what) { case EVENT_SELECT_FILE_DONE: - readBinary(); + response = (IccIoResult) ar.result; + if (response.getException() == null) { + readBinary(); + } else { + log("Select file error : " + response.getException()); + AsyncResult.forMessage(mCallback, null, response.getException()); + mCallback.sendToTarget(); + } break; case EVENT_READ_BINARY_DONE: - IccIoResult response = (IccIoResult) ar.result; + response = (IccIoResult) ar.result; String result = IccUtils.bytesToHexString(response.payload) .toUpperCase(Locale.US); log("IccIoResult: " + response + " payload: " + result); diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java index fadbff185c..dfb91a52b1 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java @@ -16,12 +16,15 @@ package com.android.internal.telephony; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.isA; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -97,6 +100,7 @@ public class CarrierServiceStateTrackerTest extends TelephonyTest { private void setDefaultValues() { mBundle.putInt(CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, 0); mBundle.putInt(CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT, 0); + mBundle.putBoolean(CarrierConfigManager.KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false); } @After @@ -232,4 +236,39 @@ public class CarrierServiceStateTrackerTest extends TelephonyTest { verify(mNotificationManager, atLeast(2)).cancel( CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG, SUB_ID); } + + @Test + public void testSetEnabledNotifications() { + logd(LOG_TAG + ":testSetEnabledNotifications()"); + + mBundle.putBoolean(CarrierConfigManager.KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, true); + + Notification.Builder mNotificationBuilder = new Notification.Builder(mContext); + doReturn(mNotificationBuilder).when(mSpyCarrierSST).getNotificationBuilder(any()); + doReturn(mNotificationManager).when(mSpyCarrierSST).getNotificationManager(any()); + doReturn(true).when(mPhone).isWifiCallingEnabled(); // notifiable for emergency + mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */, SUB_ID, + TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID); + processAllMessages(); + + Map<Integer, CarrierServiceStateTracker.NotificationType> notificationTypeMap = + mCarrierSST.getNotificationTypeMap(); + CarrierServiceStateTracker.NotificationType prefNetworkNotification = + notificationTypeMap.get(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK); + CarrierServiceStateTracker.NotificationType emergencyNetworkNotification = + notificationTypeMap.get(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK); + assertFalse(prefNetworkNotification.isEnabled()); + assertTrue(emergencyNetworkNotification.isEnabled()); + + verify(mNotificationManager, never()).notify( + eq(CarrierServiceStateTracker.PREF_NETWORK_NOTIFICATION_TAG), + eq(SUB_ID), isA(Notification.class)); + verify(mNotificationManager, atLeast(1)).cancel( + CarrierServiceStateTracker.PREF_NETWORK_NOTIFICATION_TAG, SUB_ID); + verify(mNotificationManager, atLeast(1)).notify( + eq(CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG), + eq(SUB_ID), isA(Notification.class)); + verify(mNotificationManager, never()).cancel( + CarrierServiceStateTracker.EMERGENCY_NOTIFICATION_TAG, SUB_ID); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java index c5f20e39a4..465880aa7d 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java @@ -2211,7 +2211,6 @@ public class GsmCdmaPhoneTest extends TelephonyTest { verify(mMockCi, times(1)).setNullCipherAndIntegrityEnabled(anyBoolean(), any(Message.class)); - // Some ephemeral error occurred in the modem, but the feature was supported mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE, new AsyncResult(null, null, new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED)))); @@ -2220,6 +2219,28 @@ public class GsmCdmaPhoneTest extends TelephonyTest { } @Test + public void testHandleNullCipherAndIntegrityEnabled_radioUnavailable() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CELLULAR_SECURITY, + TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, Boolean.TRUE.toString(), + false); + mPhoneUT.mCi = mMockCi; + assertFalse(mPhoneUT.isNullCipherAndIntegritySupported()); + + mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_RADIO_AVAILABLE, + new AsyncResult(null, new int[]{ServiceState.RIL_RADIO_TECHNOLOGY_GSM}, null))); + processAllMessages(); + + verify(mMockCi, times(1)).setNullCipherAndIntegrityEnabled(anyBoolean(), + any(Message.class)); + + mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE, + new AsyncResult(null, null, + new CommandException(CommandException.Error.RADIO_NOT_AVAILABLE)))); + processAllMessages(); + assertFalse(mPhoneUT.isNullCipherAndIntegritySupported()); + } + + @Test public void testHandleNullCipherAndIntegrityEnabled_radioSupportsFeature() { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CELLULAR_SECURITY, TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, Boolean.TRUE.toString(), @@ -2234,7 +2255,6 @@ public class GsmCdmaPhoneTest extends TelephonyTest { verify(mMockCi, times(1)).setNullCipherAndIntegrityEnabled(anyBoolean(), any(Message.class)); - // Some ephemeral error occurred in the modem, but the feature was supported mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE, new AsyncResult(null, null, null))); processAllMessages(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java index 1c44772d88..fe1404bcef 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java @@ -342,9 +342,6 @@ public class ImsSmsDispatcherTest extends TelephonyTest { public void testFallbackGsmRetrywithMessageRef() throws Exception { int token = mImsSmsDispatcher.mNextToken.get(); int messageRef = mImsSmsDispatcher.nextMessageRef(); - if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) { - messageRef += 1; - } when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP); when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM); @@ -363,11 +360,7 @@ public class ImsSmsDispatcherTest extends TelephonyTest { ArgumentCaptor<SMSDispatcher.SmsTracker> captor = ArgumentCaptor.forClass(SMSDispatcher.SmsTracker.class); verify(mSmsDispatchersController).sendRetrySms(captor.capture()); - if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) { - assertTrue(messageRef + 1 == captor.getValue().mMessageRef); - } else { - assertTrue(messageRef == captor.getValue().mMessageRef); - } + assertTrue(messageRef == captor.getValue().mMessageRef); } @Test diff --git a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java index 19be558088..03b1cfddb8 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java @@ -319,4 +319,19 @@ public class LocaleTrackerTest extends TelephonyTest { mLocaleTracker.updateOperatorNumeric(TEST_CELL_MCC + FAKE_MNC); verify(mNitzStateMachine, times(1)).handleCountryDetected(""); } + + @Test + public void testClearCellInfoForLostOperator() { + doReturn(true).when(mPhone).isRadioOn(); + sendServiceState(ServiceState.STATE_OUT_OF_SERVICE); + processAllMessages(); + assertTrue(mLocaleTracker.isTracking()); + assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry()); + + // airplane mode + VoWiFI case + doReturn(false).when(mPhone).isRadioOn(); + sendServiceState(ServiceState.STATE_POWER_OFF); + sendServiceState(ServiceState.STATE_IN_SERVICE); + assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry()); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java index f4c19d974a..a41dbe15f7 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java @@ -43,6 +43,7 @@ import static org.mockito.Mockito.verify; import android.content.Intent; import android.content.res.Resources; +import android.os.AsyncResult; import android.os.HandlerThread; import android.os.ParcelUuid; import android.os.PersistableBundle; @@ -59,6 +60,7 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.telephony.data.DataSettingsManager; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; +import com.android.internal.telephony.test.SimulatedCommands; import org.junit.After; import org.junit.Assert; @@ -302,9 +304,18 @@ public class MultiSimSettingControllerTest extends TelephonyTest { @Test public void testSubInfoChangeAfterRadioUnavailable() throws Exception { + int phone1SubId = 1; + int phone2SubId = 2; + // Mock DSDS, mock Phone 2 + SimulatedCommands simulatedCommands2 = mock(SimulatedCommands.class); + mPhone2.mCi = simulatedCommands2; + doReturn(mDataSettingsManagerMock2).when(mPhone2).getDataSettingsManager(); + mPhones = new Phone[]{mPhone, mPhone2}; + replaceInstance(PhoneFactory.class, "sPhones", null, mPhones); + // Load carrier config for all subs mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded(); - sendCarrierConfigChanged(0, 1); - sendCarrierConfigChanged(1, 2); + sendCarrierConfigChanged(0, phone1SubId); + sendCarrierConfigChanged(1, phone2SubId); processAllMessages(); // Ensure all subscription loaded only updates state once @@ -315,7 +326,22 @@ public class MultiSimSettingControllerTest extends TelephonyTest { verify(mSubscriptionManagerService, never()).setDefaultVoiceSubId(anyInt()); verify(mSubscriptionManagerService, never()).setDefaultSmsSubId(anyInt()); - // Notify radio unavailable. + // DSDS -> single active modem, radio available on phone 0 but unavailable on phone 1 + doReturn(TelephonyManager.RADIO_POWER_UNAVAILABLE).when(simulatedCommands2).getRadioState(); + markSubscriptionInactive(phone2SubId); + AsyncResult result = new AsyncResult(null, 1/*activeModemCount*/, null); + clearInvocations(mSubscriptionManagerService); + mMultiSimSettingControllerUT.obtainMessage( + MultiSimSettingController.EVENT_MULTI_SIM_CONFIG_CHANGED, result).sendToTarget(); + mMultiSimSettingControllerUT.notifySubscriptionInfoChanged(); + processAllMessages(); + + // Should still set defaults to the only remaining sub + verify(mSubscriptionManagerService).setDefaultDataSubId(phone1SubId); + verify(mSubscriptionManagerService).setDefaultVoiceSubId(phone1SubId); + verify(mSubscriptionManagerService).setDefaultSmsSubId(phone1SubId); + + // Notify radio unavailable on all subs. replaceInstance(BaseCommands.class, "mState", mSimulatedCommands, TelephonyManager.RADIO_POWER_UNAVAILABLE); mMultiSimSettingControllerUT.obtainMessage( @@ -323,7 +349,6 @@ public class MultiSimSettingControllerTest extends TelephonyTest { // Mark all subs as inactive. markSubscriptionInactive(1); - markSubscriptionInactive(2); clearInvocations(mSubscriptionManagerService); // The below sub info change should be ignored. diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java index 8dc8ea710f..f5bdd27b6b 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java @@ -16,6 +16,8 @@ package com.android.internal.telephony; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import android.compat.testing.PlatformCompatChangeRule; @@ -54,6 +56,7 @@ public class NetworkRegistrationInfoTest { .setAvailableServices(Arrays.asList(NetworkRegistrationInfo.SERVICE_TYPE_DATA)) .setCellIdentity(new CellIdentityLte()) .setRegisteredPlmn("12345") + .setIsNonTerrestrialNetwork(true) .build(); Parcel p = Parcel.obtain(); @@ -70,6 +73,7 @@ public class NetworkRegistrationInfoTest { public void testDefaultValues() { NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder().build(); assertEquals("", nri.getRegisteredPlmn()); + assertThat(nri.isNonTerrestrialNetwork()).isEqualTo(false); } @Test @@ -77,6 +81,8 @@ public class NetworkRegistrationInfoTest { public void testBuilder() { assertEquals("12345", new NetworkRegistrationInfo.Builder() .setRegisteredPlmn("12345").build().getRegisteredPlmn()); + assertThat(new NetworkRegistrationInfo.Builder().setIsNonTerrestrialNetwork(true).build() + .isNonTerrestrialNetwork()).isEqualTo(true); } @Test @@ -139,4 +145,11 @@ public class NetworkRegistrationInfoTest { assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY, nri.getRegistrationState()); } + + @Test + public void testSetIsNonTerrestrialNetwork() { + NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder().build(); + nri.setIsNonTerrestrialNetwork(true); + assertThat(nri.isNonTerrestrialNetwork()).isEqualTo(true); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java index 4b91207e31..7f15af8051 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java @@ -34,6 +34,7 @@ import android.os.Looper; import android.os.PersistableBundle; import android.os.PowerManager; import android.telephony.CarrierConfigManager; +import android.telephony.CellInfo; import android.telephony.NetworkRegistrationInfo; import android.telephony.PhysicalChannelConfig; import android.telephony.RadioAccessFamily; @@ -282,7 +283,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); setPhysicalLinkStatus(false); - mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_idle", getCurrentState().getName()); @@ -330,7 +331,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); setPhysicalLinkStatus(true); - mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_con", getCurrentState().getName()); @@ -386,7 +387,9 @@ public class NetworkTypeControllerTest extends TelephonyTest { mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY, new int[]{41}); PhysicalChannelConfig physicalChannelConfig = new PhysicalChannelConfig.Builder() + .setPhysicalCellId(1) .setNetworkType(TelephonyManager.NETWORK_TYPE_NR) + .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING) .setBand(41) .build(); List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>(); @@ -394,7 +397,36 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList(); sendCarrierConfigChanged(); - mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); + processAllMessages(); + assertEquals("connected_mmwave", getCurrentState().getName()); + } + + @Test + public void testTransitionToCurrentStateNrConnectedMmwaveWithAdditionalBandAndNoMmwaveNrNsa() + throws Exception { + assertEquals("DefaultState", getCurrentState().getName()); + doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); + doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange(); + mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY, + new int[]{41}); + PhysicalChannelConfig ltePhysicalChannelConfig = new PhysicalChannelConfig.Builder() + .setPhysicalCellId(1) + .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE) + .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING) + .build(); + PhysicalChannelConfig nrPhysicalChannelConfig = new PhysicalChannelConfig.Builder() + .setPhysicalCellId(2) + .setNetworkType(TelephonyManager.NETWORK_TYPE_NR) + .setCellConnectionStatus(CellInfo.CONNECTION_SECONDARY_SERVING) + .setBand(41) + .build(); + List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>(); + lastPhysicalChannelConfigList.add(ltePhysicalChannelConfig); + lastPhysicalChannelConfigList.add(nrPhysicalChannelConfig); + doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList(); + sendCarrierConfigChanged(); + mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected_mmwave", getCurrentState().getName()); @@ -417,7 +449,6 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList(); sendCarrierConfigChanged(); - mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("connected", getCurrentState().getName()); @@ -535,6 +566,169 @@ public class NetworkTypeControllerTest extends TelephonyTest { } @Test + public void testEventPhysicalChannelConfigChangedWithRatcheting() throws Exception { + testTransitionToCurrentStateNrConnected(); + mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY, + new int[]{41, 77}); + mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000); + mBundle.putBoolean(CarrierConfigManager.KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL, + true); + sendCarrierConfigChanged(); + + // Primary serving NR PCC with cell ID = 1, band = none, bandwidth = 200000 + PhysicalChannelConfig pcc1 = new PhysicalChannelConfig.Builder() + .setNetworkType(TelephonyManager.NETWORK_TYPE_NR) + .setPhysicalCellId(1) + .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING) + .setCellBandwidthDownlinkKhz(19999) + .build(); + // Secondary serving NR PCC with cell ID = 2, band = 41, bandwidth = 10000 + PhysicalChannelConfig pcc2 = new PhysicalChannelConfig.Builder() + .setNetworkType(TelephonyManager.NETWORK_TYPE_NR) + .setPhysicalCellId(2) + .setCellConnectionStatus(CellInfo.CONNECTION_SECONDARY_SERVING) + .setCellBandwidthDownlinkKhz(10000) + .setBand(41) + .build(); + // Primary serving NR PCC with cell ID = 3, band = 77, bandwidth = 0 + PhysicalChannelConfig pcc3 = new PhysicalChannelConfig.Builder() + .setNetworkType(TelephonyManager.NETWORK_TYPE_NR) + .setPhysicalCellId(3) + .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING) + .setBand(77) + .build(); + + List<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>(); + physicalChannelConfigs.add(pcc1); + physicalChannelConfigs.add(pcc2); + doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList(); + + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); + processAllMessages(); + assertEquals("connected_mmwave", getCurrentState().getName()); + + // bands and bandwidths should stay ratcheted even if an empty PCC list is sent + doReturn(new ArrayList<>()).when(mSST).getPhysicalChannelConfigList(); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); + processAllMessages(); + assertEquals("connected_mmwave", getCurrentState().getName()); + + // bands and bandwidths should stay ratcheted as long as anchor NR cell is the same + physicalChannelConfigs.remove(pcc2); + doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList(); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); + processAllMessages(); + assertEquals("connected_mmwave", getCurrentState().getName()); + + // bands and bandwidths should no longer be ratcheted if anchor NR cell changes + // add pcc3 to front of list to ensure anchor NR cell changes from 1 -> 3 + physicalChannelConfigs.add(0, pcc3); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); + processAllMessages(); + assertEquals("connected", getCurrentState().getName()); + + physicalChannelConfigs.add(pcc2); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); + processAllMessages(); + assertEquals("connected_mmwave", getCurrentState().getName()); + } + + @Test + public void testEventPhysicalChannelConfigChangedWithoutRatcheting() throws Exception { + testTransitionToCurrentStateNrConnected(); + mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY, + new int[]{41, 77}); + mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000); + sendCarrierConfigChanged(); + + // Primary serving NR PCC with cell ID = 1, band = none, bandwidth = 200000 + PhysicalChannelConfig pcc1 = new PhysicalChannelConfig.Builder() + .setNetworkType(TelephonyManager.NETWORK_TYPE_NR) + .setPhysicalCellId(1) + .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING) + .setCellBandwidthDownlinkKhz(19999) + .build(); + // Secondary serving NR PCC with cell ID = 2, band = 41, bandwidth = 10000 + PhysicalChannelConfig pcc2 = new PhysicalChannelConfig.Builder() + .setNetworkType(TelephonyManager.NETWORK_TYPE_NR) + .setPhysicalCellId(2) + .setCellConnectionStatus(CellInfo.CONNECTION_SECONDARY_SERVING) + .setCellBandwidthDownlinkKhz(10000) + .setBand(41) + .build(); + + List<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>(); + physicalChannelConfigs.add(pcc1); + physicalChannelConfigs.add(pcc2); + doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList(); + + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); + processAllMessages(); + assertEquals("connected_mmwave", getCurrentState().getName()); + + // bands and bandwidths should stay ratcheted even if an empty PCC list is sent + doReturn(new ArrayList<>()).when(mSST).getPhysicalChannelConfigList(); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); + processAllMessages(); + assertEquals("connected_mmwave", getCurrentState().getName()); + + // bands and bandwidths should change if PCC list changes + physicalChannelConfigs.remove(pcc2); + doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList(); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); + processAllMessages(); + assertEquals("connected", getCurrentState().getName()); + } + + @Test + public void testEventPhysicalChannelConfigChangedUsingUserDataForRrc() throws Exception { + testTransitionToCurrentStateNrConnected(); + mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY, + new int[]{41, 77}); + mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000); + mBundle.putBoolean(CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, + true); + sendCarrierConfigChanged(); + + // Primary serving NR PCC with cell ID = 1, band = none, bandwidth = 200000 + PhysicalChannelConfig pcc1 = new PhysicalChannelConfig.Builder() + .setNetworkType(TelephonyManager.NETWORK_TYPE_NR) + .setPhysicalCellId(1) + .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING) + .setCellBandwidthDownlinkKhz(19999) + .build(); + // Secondary serving NR PCC with cell ID = 2, band = 41, bandwidth = 10000 + PhysicalChannelConfig pcc2 = new PhysicalChannelConfig.Builder() + .setNetworkType(TelephonyManager.NETWORK_TYPE_NR) + .setPhysicalCellId(2) + .setCellConnectionStatus(CellInfo.CONNECTION_SECONDARY_SERVING) + .setCellBandwidthDownlinkKhz(10000) + .setBand(41) + .build(); + + List<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>(); + physicalChannelConfigs.add(pcc1); + physicalChannelConfigs.add(pcc2); + doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList(); + + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); + processAllMessages(); + assertEquals("connected_mmwave", getCurrentState().getName()); + + // bands and bandwidths should not stay the same even if an empty PCC list is sent + doReturn(new ArrayList<>()).when(mSST).getPhysicalChannelConfigList(); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); + processAllMessages(); + assertEquals("connected", getCurrentState().getName()); + + // bands and bandwidths should change if PCC list changes + doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList(); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); + processAllMessages(); + assertEquals("connected_mmwave", getCurrentState().getName()); + } + + @Test public void testNrPhysicalChannelChangeFromNrConnectedMmwaveToLteConnected() throws Exception { testTransitionToCurrentStateNrConnectedMmwave(); doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); @@ -556,7 +750,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { testTransitionToCurrentStateNrConnectedMmwave(); doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState(); setPhysicalLinkStatus(true); - mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); @@ -592,7 +786,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { doReturn(true).when(mServiceState).isUsingCarrierAggregation(); doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths(); - mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA, @@ -620,7 +814,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { // LTE -> LTE+ doReturn(true).when(mServiceState).isUsingCarrierAggregation(); doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths(); - mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA, @@ -648,7 +842,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { // LTE -> LTE+ doReturn(true).when(mServiceState).isUsingCarrierAggregation(); doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths(); - mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA, @@ -676,7 +870,7 @@ public class NetworkTypeControllerTest extends TelephonyTest { testTransitionToCurrentStateLteConnectedSupportPhysicalChannelConfig1_6(); doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); setPhysicalLinkStatus(false); - mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */); + mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */); mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */); processAllMessages(); assertEquals("not_restricted_rrc_idle", getCurrentState().getName()); @@ -1277,27 +1471,9 @@ public class NetworkTypeControllerTest extends TelephonyTest { List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>(); lastPhysicalChannelConfigList.add(new PhysicalChannelConfig.Builder() .setNetworkType(TelephonyManager.NETWORK_TYPE_NR) - .setCellBandwidthDownlinkKhz(20001) - .build()); - doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList(); - mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000); - sendCarrierConfigChanged(); - - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); - processAllMessages(); - assertEquals("connected_mmwave", getCurrentState().getName()); - } - - @Test - public void testTransitionToCurrentStateNrConnectedWithHighBandwidthIncludingLte() - throws Exception { - assertEquals("DefaultState", getCurrentState().getName()); - doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState(); - doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange(); - List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>(); - lastPhysicalChannelConfigList.add(new PhysicalChannelConfig.Builder() - .setNetworkType(TelephonyManager.NETWORK_TYPE_NR) - .setCellBandwidthDownlinkKhz(20000) + .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING) + .setPhysicalCellId(1) + .setCellBandwidthDownlinkKhz(19999) .build()); lastPhysicalChannelConfigList.add(new PhysicalChannelConfig.Builder() .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE) @@ -1305,13 +1481,13 @@ public class NetworkTypeControllerTest extends TelephonyTest { .build()); doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList(); mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000); + sendCarrierConfigChanged(); + assertEquals("connected", getCurrentState().getName()); + mBundle.putBoolean( CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, true); sendCarrierConfigChanged(); - - mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE); - processAllMessages(); assertEquals("connected_mmwave", getCurrentState().getName()); } diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java index 500d69cb44..7efb886bc9 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java @@ -18,6 +18,8 @@ package com.android.internal.telephony; import static android.telephony.ServiceState.UNKNOWN_ID; +import static com.google.common.truth.Truth.assertThat; + import android.os.Bundle; import android.os.Parcel; import android.telephony.AccessNetworkConstants; @@ -444,6 +446,18 @@ public class ServiceStateTest extends TestCase { assertEquals(UNKNOWN_ID, coarseLocationSanitizedSs.getCdmaNetworkId()); } + @SmallTest + public void testIsUsingNonTerrestrialNetwork() { + ServiceState ss = new ServiceState(); + assertThat(ss.isUsingNonTerrestrialNetwork()).isEqualTo(false); + + NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder() + .setIsNonTerrestrialNetwork(true) + .build(); + ss.addNetworkRegistrationInfo(nri); + assertThat(ss.isUsingNonTerrestrialNetwork()).isEqualTo(true); + } + private void assertCellIdentitiesSanitized(ServiceState ss) { List<NetworkRegistrationInfo> networkRegistrationInfoList = ss.getNetworkRegistrationInfoList(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java index 846b48e3f7..5f592d19e0 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java @@ -16,6 +16,9 @@ package com.android.internal.telephony; +import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY; +import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_SMS; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; @@ -96,6 +99,7 @@ import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager; import com.android.internal.telephony.data.AccessNetworksManager; import com.android.internal.telephony.data.DataNetworkController; import com.android.internal.telephony.metrics.ServiceStateStats; +import com.android.internal.telephony.satellite.SatelliteController; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.test.SimulatedCommands; import com.android.internal.telephony.uicc.IccCardApplicationStatus; @@ -116,6 +120,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.concurrent.Executor; +import java.util.stream.Collectors; public class ServiceStateTrackerTest extends TelephonyTest { // Mocked classes @@ -138,6 +143,7 @@ public class ServiceStateTrackerTest extends TelephonyTest { private ServiceStateTracker sst; private ServiceStateTrackerTestHandler mSSTTestHandler; private PersistableBundle mBundle; + private SatelliteController mSatelliteController; private static final int EVENT_REGISTERED_TO_NETWORK = 1; private static final int EVENT_SUBSCRIPTION_INFO_READY = 2; @@ -248,6 +254,11 @@ public class ServiceStateTrackerTest extends TelephonyTest { mSubInfoInternal = new SubscriptionInfoInternal.Builder().setId(1).build(); mServiceStateStats = Mockito.mock(ServiceStateStats.class); + mSatelliteController = Mockito.mock(SatelliteController.class); + replaceInstance(SatelliteController.class, "sInstance", null, + mSatelliteController); + doReturn(new ArrayList<>()).when(mSatelliteController).getSatellitePlmnList(); + mContextFixture.putResource(R.string.kg_text_message_separator, " \u2014 "); doReturn(mSubInfoInternal).when(mSubscriptionManagerService) @@ -2763,6 +2774,11 @@ public class ServiceStateTrackerTest extends TelephonyTest { ss.setEmergencyOnly(true); sst.mSS = ss; + // The other phone is in service + ss = new ServiceState(); + doReturn(ss).when(mSST).getServiceState(); + doReturn(ServiceState.STATE_IN_SERVICE).when(mSST).getCombinedRegState(ss); + // update the spn sst.updateSpnDisplay(); @@ -3208,4 +3224,55 @@ public class ServiceStateTrackerTest extends TelephonyTest { assertTrue(sst.mSS.isIwlanPreferred()); } + + @Test + public void testRegisterToSatellite() { + int[] satelliteSupportedServices = {SERVICE_TYPE_SMS, SERVICE_TYPE_EMERGENCY}; + List<Integer> satelliteSupportedServiceList = + Arrays.stream(satelliteSupportedServices).boxed().collect(Collectors.toList()); + CellIdentityGsm cellIdentity = + new CellIdentityGsm(0, 1, 900, 5, "101", "23", "test", "tst", + Collections.emptyList()); + doReturn(Arrays.asList("10123")).when(mSatelliteController).getSatellitePlmnList(); + doReturn(satelliteSupportedServiceList).when(mSatelliteController) + .getSupportedSatelliteServices(sst.mSubId, "10123"); + + assertFalse(sst.mSS.isUsingNonTerrestrialNetwork()); + + // Data registered to satellite roaming PLMN - "00101" + NetworkRegistrationInfo dataReg = new NetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + 5, 16, 0, false, null, cellIdentity, getPlmnFromCellIdentity(cellIdentity), + 1, false, false, false, null); + + // CS out of service + NetworkRegistrationInfo voiceReg = new NetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + 0, 16, 0, true, null, cellIdentity, getPlmnFromCellIdentity(cellIdentity), + false, 0, 0, 0); + + sst.mPollingContext[0] = 2; + // Update voice reg state to be in oos + sst.sendMessage(sst.obtainMessage( + ServiceStateTracker.EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION, + new AsyncResult(sst.mPollingContext, voiceReg, null))); + waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); + + // Update data registered to satellite roaming PLMN + sst.sendMessage(sst.obtainMessage( + ServiceStateTracker.EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION, + new AsyncResult(sst.mPollingContext, dataReg, null))); + waitForLastHandlerAction(mSSTTestHandler.getThreadHandler()); + + assertTrue(sst.mSS.isUsingNonTerrestrialNetwork()); + List<NetworkRegistrationInfo> nriList = + sst.mSS.getNetworkRegistrationInfoListForTransportType( + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + assertEquals(2, nriList.size()); + for (NetworkRegistrationInfo nri : nriList) { + assertTrue(Arrays.equals(satelliteSupportedServices, nri.getAvailableServices().stream() + .mapToInt(Integer::intValue) + .toArray())); + } + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java index b044814765..705bafdf75 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java @@ -219,6 +219,7 @@ public abstract class TelephonyTest { protected UiccCardApplication mUiccCardApplication3gpp2; protected UiccCardApplication mUiccCardApplicationIms; protected SIMRecords mSimRecords; + protected SignalStrengthController mSignalStrengthController; protected RuimRecords mRuimRecords; protected IsimUiccRecords mIsimUiccRecords; protected ProxyController mProxyController; @@ -455,6 +456,7 @@ public abstract class TelephonyTest { mUiccCardApplication3gpp2 = Mockito.mock(UiccCardApplication.class); mUiccCardApplicationIms = Mockito.mock(UiccCardApplication.class); mSimRecords = Mockito.mock(SIMRecords.class); + mSignalStrengthController = Mockito.mock(SignalStrengthController.class); mRuimRecords = Mockito.mock(RuimRecords.class); mIsimUiccRecords = Mockito.mock(IsimUiccRecords.class); mProxyController = Mockito.mock(ProxyController.class); @@ -636,6 +638,7 @@ public abstract class TelephonyTest { doReturn(mSST).when(mPhone).getServiceStateTracker(); doReturn(mDeviceStateMonitor).when(mPhone).getDeviceStateMonitor(); doReturn(mDisplayInfoController).when(mPhone).getDisplayInfoController(); + doReturn(mSignalStrengthController).when(mPhone).getSignalStrengthController(); doReturn(mEmergencyNumberTracker).when(mPhone).getEmergencyNumberTracker(); doReturn(mCarrierSignalAgent).when(mPhone).getCarrierSignalAgent(); doReturn(mCarrierActionAgent).when(mPhone).getCarrierActionAgent(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java new file mode 100644 index 0000000000..7ac3a170ad --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java @@ -0,0 +1,380 @@ +/* + * 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.data; + +import static android.telephony.SubscriptionManager.DEFAULT_PHONE_INDEX; + +import static com.android.internal.telephony.data.AutoDataSwitchController.EVALUATION_REASON_DATA_SETTINGS_CHANGED; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.NotificationManager; +import android.content.Context; +import android.net.NetworkCapabilities; +import android.os.AsyncResult; +import android.os.Looper; +import android.os.Message; +import android.telephony.AccessNetworkConstants; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.ServiceState; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneFactory; +import com.android.internal.telephony.TelephonyTest; +import com.android.internal.telephony.subscription.SubscriptionInfoInternal; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class AutoDataSwitchControllerTest extends TelephonyTest { + private static final int EVENT_SERVICE_STATE_CHANGED = 1; + private static final int EVENT_DISPLAY_INFO_CHANGED = 2; + private static final int EVENT_EVALUATE_AUTO_SWITCH = 3; + private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 4; + private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE = 5; + + private static final int PHONE_1 = 0; + private static final int SUB_1 = 1; + private static final int PHONE_2 = 1; + private static final int SUB_2 = 2; + private static final int MAX_RETRY = 5; + // Mocked + private AutoDataSwitchController.AutoDataSwitchControllerCallback mMockedPhoneSwitcherCallback; + + private int mDefaultDataSub; + private AutoDataSwitchController mAutoDataSwitchControllerUT; + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + mMockedPhoneSwitcherCallback = + mock(AutoDataSwitchController.AutoDataSwitchControllerCallback.class); + + doReturn(PHONE_1).when(mPhone).getPhoneId(); + doReturn(SUB_1).when(mPhone).getSubId(); + + doReturn(PHONE_2).when(mPhone2).getPhoneId(); + doReturn(SUB_2).when(mPhone2).getSubId(); + + doReturn(SUB_1).when(mSubscriptionManagerService).getSubId(PHONE_1); + doReturn(SUB_2).when(mSubscriptionManagerService).getSubId(PHONE_2); + + mPhones = new Phone[]{mPhone, mPhone2}; + for (Phone phone : mPhones) { + doReturn(mSST).when(phone).getServiceStateTracker(); + doReturn(mDisplayInfoController).when(phone).getDisplayInfoController(); + doReturn(mSignalStrengthController).when(phone).getSignalStrengthController(); + doReturn(mSignalStrength).when(phone).getSignalStrength(); + doAnswer(invocation -> phone.getSubId() == mDefaultDataSub) + .when(phone).isUserDataEnabled(); + } + doReturn(new int[mPhones.length]).when(mSubscriptionManagerService) + .getActiveSubIdList(true); + doAnswer(invocation -> { + int subId = (int) invocation.getArguments()[0]; + + if (!SubscriptionManager.isUsableSubIdValue(subId)) return null; + + int slotIndex = subId == SUB_1 ? PHONE_1 : PHONE_2; + return new SubscriptionInfoInternal.Builder() + .setSimSlotIndex(slotIndex).setId(subId).build(); + }).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt()); + replaceInstance(PhoneFactory.class, "sPhones", null, mPhones); + + // Change resource overlay + doReturn(true).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired(); + doReturn(1L).when(mDataConfigManager) + .getAutoDataSwitchAvailabilityStabilityTimeThreshold(); + doReturn(MAX_RETRY).when(mDataConfigManager).getAutoDataSwitchValidationMaxRetry(); + + setDefaultDataSubId(SUB_1); + doReturn(PHONE_1).when(mPhoneSwitcher).getPreferredDataPhoneId(); + + mAutoDataSwitchControllerUT = new AutoDataSwitchController(mContext, Looper.myLooper(), + mPhoneSwitcher, mMockedPhoneSwitcherCallback); + } + + @After + public void tearDown() throws Exception { + mAutoDataSwitchControllerUT = null; + super.tearDown(); + } + + @Test + public void testCancelSwitch_onPrimary() { + // 0. When all conditions met + prepareIdealUsesNonDdsCondition(); + processAllFutureMessages(); + + // Verify attempting to switch + verify(mMockedPhoneSwitcherCallback).onRequireValidation(PHONE_2, true/*needValidation*/); + + // 1. Service state becomes not ideal - primary is available again + serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + processAllFutureMessages(); + + verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation(); + + // 2.1 User data disabled on primary SIM + prepareIdealUsesNonDdsCondition(); + processAllFutureMessages(); + clearInvocations(mMockedPhoneSwitcherCallback); + doReturn(false).when(mPhone).isUserDataEnabled(); + mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED); + processAllFutureMessages(); + + verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation(); + + // 2.2 Auto switch feature is disabled + prepareIdealUsesNonDdsCondition(); + processAllFutureMessages(); + clearInvocations(mMockedPhoneSwitcherCallback); + doReturn(false).when(mPhone2).isDataAllowed(); + mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED); + processAllFutureMessages(); + + verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation(); + + // 3.1 No default network + prepareIdealUsesNonDdsCondition(); + processAllFutureMessages(); + clearInvocations(mMockedPhoneSwitcherCallback); + mAutoDataSwitchControllerUT.updateDefaultNetworkCapabilities(new NetworkCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)); + processAllFutureMessages(); + + verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation(); + } + + @Test + public void testOnNonDdsSwitchBackToPrimary() { + doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId(); + + prepareIdealUsesNonDdsCondition(); + // 1.1 service state changes - primary becomes available again, require validation + serviceStateChanged(PHONE_1, + NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING/*need validate*/); + processAllFutureMessages(); + verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX, + true/*needValidation*/); + + clearInvocations(mMockedPhoneSwitcherCallback); + prepareIdealUsesNonDdsCondition(); + // 1.2 service state changes - secondary becomes unavailable, NO need validation + serviceStateChanged(PHONE_1, + NetworkRegistrationInfo.REGISTRATION_STATE_HOME/*need validate*/); + serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING/*no need*/); + processAllFutureMessages(); + // The later validation requirement overrides the previous + verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX, + false/*needValidation*/); + + clearInvocations(mMockedPhoneSwitcherCallback); + prepareIdealUsesNonDdsCondition(); + // 2.1 User data disabled on primary SIM, no need validation + doReturn(false).when(mPhone).isUserDataEnabled(); + mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED); + processAllFutureMessages(); + + verify(mMockedPhoneSwitcherCallback).onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX, + EVALUATION_REASON_DATA_SETTINGS_CHANGED); + + clearInvocations(mMockedPhoneSwitcherCallback); + prepareIdealUsesNonDdsCondition(); + // 2.2 Auto switch feature is disabled, no need validation + clearInvocations(mCellularNetworkValidator); + doReturn(false).when(mPhone2).isDataAllowed(); + mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED); + processAllFutureMessages(); + + verify(mMockedPhoneSwitcherCallback).onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX, + EVALUATION_REASON_DATA_SETTINGS_CHANGED); + + clearInvocations(mMockedPhoneSwitcherCallback); + prepareIdealUsesNonDdsCondition(); + // 3.1 Default network is active on non-cellular transport + mAutoDataSwitchControllerUT.updateDefaultNetworkCapabilities(new NetworkCapabilities() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)); + processAllFutureMessages(); + + verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX, + false/*needValidation*/); + } + + @Test + public void testCancelSwitch_onSecondary() { + doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId(); + prepareIdealUsesNonDdsCondition(); + + // attempts the switch back due to secondary becomes ROAMING + serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING); + processAllFutureMessages(); + + verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX, + false/*needValidation*/); + + // cancel the switch back attempt due to secondary back to HOME + serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + processAllFutureMessages(); + + verify(mMockedPhoneSwitcherCallback).onRequireCancelAnyPendingAutoSwitchValidation(); + } + + @Test + public void testValidationFailedRetry() { + prepareIdealUsesNonDdsCondition(); + + for (int i = 0; i < MAX_RETRY; i++) { + mAutoDataSwitchControllerUT.evaluateRetryOnValidationFailed(); + processAllFutureMessages(); + } + verify(mMockedPhoneSwitcherCallback, times(MAX_RETRY)) + .onRequireValidation(PHONE_2, true /*need validation*/); + } + + @Test + public void testExemptPingTest() { + // Change resource overlay + doReturn(false).when(mDataConfigManager) + .isPingTestBeforeAutoDataSwitchRequired(); + mAutoDataSwitchControllerUT = new AutoDataSwitchController(mContext, Looper.myLooper(), + mPhoneSwitcher, mMockedPhoneSwitcherCallback); + + //1. DDS -> nDDS, verify callback doesn't require validation + prepareIdealUsesNonDdsCondition(); + processAllFutureMessages(); + + verify(mMockedPhoneSwitcherCallback).onRequireValidation(PHONE_2, false/*needValidation*/); + + //2. nDDS -> DDS, verify callback doesn't require validation + doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId(); + serviceStateChanged(PHONE_1, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING); + processAllFutureMessages(); + verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX, + false/*needValidation*/); + } + + @Test + public void testSetNotification() { + NotificationManager notificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + SubscriptionInfo mockedInfo = mock(SubscriptionInfo.class); + doReturn(false).when(mockedInfo).isOpportunistic(); + doReturn(mockedInfo).when(mSubscriptionManagerService).getSubscriptionInfo(anyInt()); + + // First switch is not due to auto, so no notification. + mAutoDataSwitchControllerUT.displayAutoDataSwitchNotification(PHONE_2, false); + verify(mSubscriptionManagerService, never()).getSubscriptionInfo(SUB_2); + + // Switch is due to auto, show notification. + mAutoDataSwitchControllerUT.displayAutoDataSwitchNotification(PHONE_2, true); + verify(notificationManager).notify(any(), anyInt(), any()); + verify(mSubscriptionManagerService).getSubscriptionInfo(SUB_2); + + // Switch is due to auto, but already shown notification, hide the notification. + mAutoDataSwitchControllerUT.displayAutoDataSwitchNotification(PHONE_2, true); + verify(notificationManager).cancel(any(), anyInt()); + } + + @Test + public void testMultiSimConfigChanged() { + // Test Dual -> Single + mAutoDataSwitchControllerUT.onMultiSimConfigChanged(1); + + verify(mDisplayInfoController).unregisterForTelephonyDisplayInfoChanged(any()); + verify(mSignalStrengthController).unregisterForSignalStrengthChanged(any()); + verify(mSST).unregisterForServiceStateChanged(any()); + + clearInvocations(mDisplayInfoController, mSignalStrengthController, mSST); + // Test Single -> Dual + mAutoDataSwitchControllerUT.onMultiSimConfigChanged(2); + + verify(mDisplayInfoController).registerForTelephonyDisplayInfoChanged(any(), + eq(EVENT_DISPLAY_INFO_CHANGED), eq(PHONE_2)); + verify(mSignalStrengthController).registerForSignalStrengthChanged(any(), + eq(EVENT_SIGNAL_STRENGTH_CHANGED), eq(PHONE_2)); + verify(mSST).registerForServiceStateChanged(any(), + eq(EVENT_SERVICE_STATE_CHANGED), eq(PHONE_2)); + } + + /** + * Trigger conditions + * 1. service state changes + * 2. data setting changes + * - user toggle data + * - user toggle auto switch feature + * 3. default network changes + * - current network lost + * - network become active on non-cellular network + */ + private void prepareIdealUsesNonDdsCondition() { + // 1. service state changes + serviceStateChanged(PHONE_2, NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + serviceStateChanged(PHONE_1, NetworkRegistrationInfo + .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING); + + // 2.1 User data enabled on primary SIM + doReturn(true).when(mPhone).isUserDataEnabled(); + + // 2.2 Auto switch feature is enabled + doReturn(true).when(mPhone2).isDataAllowed(); + + // 3.1 No default network + mAutoDataSwitchControllerUT.updateDefaultNetworkCapabilities(null /*networkCapabilities*/); + } + + private void serviceStateChanged(int phoneId, + @NetworkRegistrationInfo.RegistrationState int dataRegState) { + + ServiceState ss = new ServiceState(); + + ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() + .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) + .setRegistrationState(dataRegState) + .setDomain(NetworkRegistrationInfo.DOMAIN_PS) + .build()); + + ss.setDataRoamingFromRegistration(dataRegState + == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING); + + doReturn(ss).when(mPhones[phoneId]).getServiceState(); + + Message msg = mAutoDataSwitchControllerUT.obtainMessage(EVENT_SERVICE_STATE_CHANGED); + msg.obj = new AsyncResult(phoneId, null, null); + mAutoDataSwitchControllerUT.sendMessage(msg); + } + private void setDefaultDataSubId(int defaultDataSub) { + mDefaultDataSub = defaultDataSub; + doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId(); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java index f3128089aa..d4a0804b57 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java @@ -20,10 +20,16 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.os.Looper; import android.os.PersistableBundle; +import android.telephony.CarrierConfigManager; +import android.telephony.SignalStrength; +import android.telephony.TelephonyDisplayInfo; +import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -96,4 +102,47 @@ public class DataConfigManagerTest extends TelephonyTest { assertThat(invalidFormat3.timeWindow).isEqualTo(defaultValue.timeWindow); assertThat(invalidFormat3.eventNumOccurrence).isEqualTo(defaultValue.eventNumOccurrence); } + + @Test + public void testParseAutoDataSwitchScoreTable() { + SignalStrength signalStrength = mock(SignalStrength.class); + int tolerance = 100; + PersistableBundle auto_data_switch_rat_signal_score_string_bundle = new PersistableBundle(); + auto_data_switch_rat_signal_score_string_bundle.putIntArray( + "NR_NSA_MMWAVE", new int[]{10000, 10227, 12488, 15017, 15278}); + auto_data_switch_rat_signal_score_string_bundle.putIntArray( + "LTE", new int[]{-3731, 5965, 8618, 11179, 13384}); + mBundle.putPersistableBundle( + CarrierConfigManager.KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE, + auto_data_switch_rat_signal_score_string_bundle); + + mContextFixture.putIntResource(com.android.internal.R.integer + .auto_data_switch_score_tolerance, tolerance); + + mDataConfigManagerUT.sendEmptyMessage(1/*EVENT_CARRIER_CONFIG_CHANGED*/); + processAllMessages(); + + assertThat(mDataConfigManagerUT.getAutoDataSwitchScoreTolerance()).isEqualTo(tolerance); + + // Verify NSA_MMWAVE + doReturn(SignalStrength.SIGNAL_STRENGTH_POOR).when(signalStrength).getLevel(); + assertThat(mDataConfigManagerUT.getAutoDataSwitchScore(new TelephonyDisplayInfo( + TelephonyManager.NETWORK_TYPE_LTE, + TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, false/*isRoaming*/), + signalStrength)).isEqualTo(10227); + // Verify if entry contains any invalid negative scores, should yield -1. + doReturn(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN).when(signalStrength).getLevel(); + assertThat(mDataConfigManagerUT.getAutoDataSwitchScore(new TelephonyDisplayInfo( + TelephonyManager.NETWORK_TYPE_LTE, + TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false/*isRoaming*/), + signalStrength)) + .isEqualTo(-1/*INVALID_AUTO_DATA_SWITCH_SCORE*/); + // Verify non-existent entry should yield -1 + doReturn(SignalStrength.SIGNAL_STRENGTH_POOR).when(signalStrength).getLevel(); + assertThat(mDataConfigManagerUT.getAutoDataSwitchScore(new TelephonyDisplayInfo( + TelephonyManager.NETWORK_TYPE_EDGE, + TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false/*isRoaming*/), + signalStrength)) + .isEqualTo(-1/*INVALID_AUTO_DATA_SWITCH_SCORE*/); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java index 808a15d1d9..d96bac49b8 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java @@ -35,6 +35,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; @@ -62,6 +63,7 @@ import android.provider.Telephony; import android.telephony.AccessNetworkConstants; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.AccessNetworkConstants.TransportType; +import android.telephony.Annotation; import android.telephony.Annotation.DataFailureCause; import android.telephony.Annotation.NetCapability; import android.telephony.Annotation.NetworkType; @@ -1584,6 +1586,14 @@ public class DataNetworkControllerTest extends TelephonyTest { // Verify data is torn down. verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + + // Registration is back to HOME. + serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, + NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + processAllFutureMessages(); + + // Verify data is restored. + verifyInternetConnected(); } @Test @@ -1630,13 +1640,27 @@ public class DataNetworkControllerTest extends TelephonyTest { doReturn(true).when(controller).isCarrierConfigLoadedForAllSub(); replaceInstance(MultiSimSettingController.class, "sInstance", null, controller); + // Mock Data Overall data is always enabled due to auto data switch, + // verify the test shouldn't rely on the overall data status + doReturn(1).when(mPhone).getSubId(); + doReturn(2).when(mSubscriptionManagerService).getDefaultDataSubId(); + Phone phone2 = Mockito.mock(Phone.class); + phone2.mCi = mSimulatedCommands; + doReturn(true).when(phone2).isUserDataEnabled(); + doReturn(mDataSettingsManager).when(phone2).getDataSettingsManager(); + replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, phone2}); + mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager + .MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true); + processAllMessages(); + clearInvocations(mPhone); + controller.notifyAllSubscriptionLoaded(); mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled( TelephonyManager.DATA_ENABLED_REASON_USER, !isDataEnabled, mContext.getOpPackageName()); processAllMessages(); - // Verify not to notify MultiSimSettingController + // Verify not to notify MultiSimSettingController due to internal calling package verify(controller, never()).notifyUserDataEnabled(anyInt(), anyBoolean()); mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled( @@ -1644,7 +1668,7 @@ public class DataNetworkControllerTest extends TelephonyTest { mContext.getOpPackageName()); processAllMessages(); - // Verify not to notify MultiSimSettingController + // Verify not to notify MultiSimSettingController due to internal calling package verify(controller, never()).notifyUserDataEnabled(anyInt(), anyBoolean()); mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled( @@ -1655,6 +1679,7 @@ public class DataNetworkControllerTest extends TelephonyTest { // Verify to notify MultiSimSettingController exactly 2 times verify(controller, times(2)).notifyUserDataEnabled(anyInt(), anyBoolean()); + verify(mPhone, never()).notifyDataEnabled(anyBoolean(), anyInt()); } @Test @@ -3208,6 +3233,14 @@ public class DataNetworkControllerTest extends TelephonyTest { processAllMessages(); verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_DUN); + + // User data disabled + mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled( + TelephonyManager.DATA_ENABLED_REASON_USER, false, mContext.getOpPackageName()); + processAllMessages(); + + // Everything should be disconnected. + verifyAllDataDisconnected(); } @Test @@ -3869,6 +3902,84 @@ public class DataNetworkControllerTest extends TelephonyTest { } @Test + public void testHandoverDataNetworkRoamingOos() throws Exception { + testSetupImsDataNetwork(); + // Configured handover is disallowed at Roaming. + mCarrierConfig.putStringArray( + CarrierConfigManager.KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, + new String[]{ + "source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN|UNKNOWN, " + + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, roaming=true, " + + "type=disallowed, capabilities=IMS" + }); + carrierConfigChanged(); + DataNetwork dataNetwork = getDataNetworks().get(0); + //Enter ROAMING + serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE, + NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING); + updateServiceStateForDatatNetwork(TelephonyManager.NETWORK_TYPE_LTE, + NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING, dataNetwork); + //OOS + serviceStateChanged(TelephonyManager.NETWORK_TYPE_UNKNOWN, + NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING); + updateServiceStateForDatatNetwork(TelephonyManager.NETWORK_TYPE_UNKNOWN, + NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, + dataNetwork); + + updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS, + AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + + // Verify IMS network was torn down on source first. + verify(mMockedWwanDataServiceManager).deactivateDataCall(anyInt(), + eq(DataService.REQUEST_REASON_NORMAL), any(Message.class)); + + // Verify that IWLAN is brought up again on IWLAN. + verify(mMockedWlanDataServiceManager).setupDataCall(anyInt(), + any(DataProfile.class), anyBoolean(), anyBoolean(), + eq(DataService.REQUEST_REASON_NORMAL), any(), anyInt(), any(), any(), anyBoolean(), + any(Message.class)); + + DataNetwork dataNetworkIwlan = getDataNetworks().get(0); + assertThat(dataNetworkIwlan.getTransport()).isEqualTo( + AccessNetworkConstants.TRANSPORT_TYPE_WLAN); + } + + private void updateServiceStateForDatatNetwork(@Annotation.NetworkType int networkType, + @NetworkRegistrationInfo.RegistrationState int regState, DataNetwork dataNetwork) { + ServiceState ss = new ServiceState(); + ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() + .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) + .setAccessNetworkTechnology(networkType) + .setRegistrationState(regState) + .setDomain(NetworkRegistrationInfo.DOMAIN_PS) + .setDataSpecificInfo(null) + .build()); + + ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() + .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN) + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN) + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME) + .setDomain(NetworkRegistrationInfo.DOMAIN_PS) + .build()); + + ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder() + .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) + .setAccessNetworkTechnology(networkType) + .setRegistrationState(regState) + .setDomain(NetworkRegistrationInfo.DOMAIN_CS) + .build()); + ss.setDataRoamingFromRegistration(regState + == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING); + doReturn(ss).when(mSST).getServiceState(); + doReturn(ss).when(mPhone).getServiceState(); + + if (dataNetwork != null) { + dataNetwork.obtainMessage(9/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget(); + processAllMessages(); + } + } + + @Test public void testHandoverDataNetworkSourceOosNoUnknownRule() throws Exception { testSetupImsDataNetwork(); // Configured handover is allowed from OOS to 4G/5G/IWLAN. diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java index b85081f1c6..41a714ca70 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java @@ -182,6 +182,28 @@ public class DataNetworkTest extends TelephonyTest { "CBS", 1).getBytes())) .build(); + private final DataProfile m5gDataProfile = new DataProfile.Builder() + .setApnSetting(new ApnSetting.Builder() + .setId(2163) + .setOperatorNumeric("12345") + .setEntryName("fake_apn") + .setApnName(null /*empty name*/) + .setUser("user") + .setPassword("passwd") + .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL) + .setProtocol(ApnSetting.PROTOCOL_IPV6) + .setRoamingProtocol(ApnSetting.PROTOCOL_IP) + .setCarrierEnabled(true) + .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE + | TelephonyManager.NETWORK_TYPE_BITMASK_NR)) + .setProfileId(1234) + .setMaxConns(321) + .setWaitTime(456) + .setMaxConnsTime(789) + .build()) + .setTrafficDescriptor(new TrafficDescriptor(null, null)) + .build(); + // Mocked classes private DataNetworkCallback mDataNetworkCallback; private DataCallSessionStats mDataCallSessionStats; @@ -1777,6 +1799,28 @@ public class DataNetworkTest extends TelephonyTest { } @Test + public void testPrivateNetwork() throws Exception { + NetworkRequestList networkRequestList = new NetworkRequestList(); + networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build(), mPhone)); + mDataNetworkUT = new DataNetwork(mPhone, Looper.myLooper(), mDataServiceManagers, + m5gDataProfile, networkRequestList, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN, DataAllowedReason.NORMAL, + mDataNetworkCallback); + replaceInstance(DataNetwork.class, "mDataCallSessionStats", + mDataNetworkUT, mDataCallSessionStats); + processAllMessages(); + + verify(mMockedWwanDataServiceManager).setupDataCall(anyInt(), + eq(m5gDataProfile), eq(false), eq(false), + eq(DataService.REQUEST_REASON_NORMAL), nullable(LinkProperties.class), + eq(DataCallResponse.PDU_SESSION_ID_NOT_SET), nullable(NetworkSliceInfo.class), + // Verify matchAllRuleAllowed is flagged true + any(TrafficDescriptor.class), eq(true), any(Message.class)); + } + + @Test public void testLinkStatusUpdate() throws Exception { setupDataNetwork(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java index e10c2a5a12..27271dfe56 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java @@ -873,8 +873,10 @@ public class DataProfileManagerTest extends TelephonyTest { @Test public void testSetInitialAttachDataProfileMultipleRequests() throws Exception { + // This test case only applies to legacy modem, see b/227579876 + doReturn(false).when(mDataConfigManager).allowClearInitialAttachDataProfile(); + // Test: Modem Cleared IA, should always send IA to modem - // TODO(b/237444788): this case should be removed from U mDataProfileManagerUT.obtainMessage(3 /* EVENT_SIM_REFRESH */).sendToTarget(); processAllMessages(); @@ -911,6 +913,14 @@ public class DataProfileManagerTest extends TelephonyTest { @Test public void testSimRemoval() { + // This test case applies to the latest modem, see b/227579876. + doReturn(true).when(mDataConfigManager).allowClearInitialAttachDataProfile(); + + // SIM inserted + mDataProfileManagerUT.obtainMessage(3 /* EVENT_SIM_REFRESH */).sendToTarget(); + processAllMessages(); + + // SIM removed Mockito.clearInvocations(mDataProfileManagerCallback); changeSimStateTo(TelephonyManager.SIM_STATE_ABSENT); mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget(); @@ -943,6 +953,58 @@ public class DataProfileManagerTest extends TelephonyTest { dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest( tnr, TelephonyManager.NETWORK_TYPE_LTE, false); assertThat(dataProfile).isEqualTo(null); + + // Verify null as initial attached data profile is sent to modem + verify(mMockedWwanDataServiceManager).setInitialAttachApn(null, false, null); + } + + @Test + public void testSimRemovalLegacy() { + // This test case only applies to legacy modem, see b/227579876, where null IA won't be + // updated to modem + doReturn(false).when(mDataConfigManager).allowClearInitialAttachDataProfile(); + + // SIM inserted + mDataProfileManagerUT.obtainMessage(3 /* EVENT_SIM_REFRESH */).sendToTarget(); + processAllMessages(); + + // SIM removed + Mockito.clearInvocations(mDataProfileManagerCallback); + mSimInserted = false; + mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget(); + processAllMessages(); + + verify(mDataProfileManagerCallback).onDataProfilesChanged(); + + TelephonyNetworkRequest tnr = new TelephonyNetworkRequest( + new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build(), mPhone); + DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest( + tnr, TelephonyManager.NETWORK_TYPE_LTE, false); + assertThat(dataProfile).isNull(); + + // expect default EIMS when SIM absent + tnr = new TelephonyNetworkRequest( + new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS) + .build(), mPhone); + dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest( + tnr, TelephonyManager.NETWORK_TYPE_LTE, false); + assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo("sos"); + + // expect no default IMS when SIM absent + tnr = new TelephonyNetworkRequest( + new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS) + .build(), mPhone); + dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest( + tnr, TelephonyManager.NETWORK_TYPE_LTE, false); + assertThat(dataProfile).isEqualTo(null); + + // Verify in legacy mode, null IA should NOT be sent to modem + verify(mMockedWwanDataServiceManager, Mockito.never()) + .setInitialAttachApn(null, false, null); } @Test diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java index 84b3302ba0..103b189f7e 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java @@ -795,7 +795,7 @@ public class DataRetryManagerTest extends TelephonyTest { // Verify scheduled via Alarm Manager ArgumentCaptor<PendingIntent> pendingIntentArgumentCaptor = ArgumentCaptor.forClass(PendingIntent.class); - verify(mAlarmManager).setAndAllowWhileIdle(anyInt(), anyLong(), + verify(mAlarmManager).setExactAndAllowWhileIdle(anyInt(), anyLong(), pendingIntentArgumentCaptor.capture()); // Verify starts retry attempt after receiving intent diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java index f7525c1e52..20cb73a68e 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java @@ -21,16 +21,22 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.os.Looper; import android.os.PersistableBundle; +import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; @@ -131,4 +137,45 @@ public class DataSettingsManagerTest extends TelephonyTest { mDataSettingsManagerUT.setDefaultDataRoamingEnabled(); assertFalse(mDataSettingsManagerUT.isDataRoamingEnabled()); } + + @Test + public void testUpdateDataEnabledAndNotifyOverride() throws Exception { + // Mock another DDS phone. + int ddsPhoneId = 1; + int ddsSubId = 2; + doReturn(ddsSubId).when(mSubscriptionManagerService).getDefaultDataSubId(); + Phone phone2 = Mockito.mock(Phone.class); + doReturn(ddsPhoneId).when(phone2).getPhoneId(); + doReturn(ddsSubId).when(phone2).getSubId(); + doReturn(ddsPhoneId).when(mSubscriptionManagerService).getPhoneId(ddsSubId); + DataSettingsManager dataSettingsManager2 = Mockito.mock(DataSettingsManager.class); + doReturn(dataSettingsManager2).when(phone2).getDataSettingsManager(); + mPhones = new Phone[] {mPhone, phone2}; + replaceInstance(PhoneFactory.class, "sPhones", null, mPhones); + ArgumentCaptor<DataSettingsManagerCallback> callbackArgumentCaptor = ArgumentCaptor + .forClass(DataSettingsManagerCallback.class); + + mDataSettingsManagerUT.sendEmptyMessage(11 /* EVENT_INITIALIZE */); + processAllMessages(); + + // Verify listening to user enabled status of other phones. + verify(dataSettingsManager2).registerCallback(callbackArgumentCaptor.capture()); + DataSettingsManagerCallback callback = callbackArgumentCaptor.getValue(); + + // Mock the phone as nonDDS. + mDataSettingsManagerUT.setDataEnabled(TelephonyManager.DATA_ENABLED_REASON_USER, false, ""); + processAllMessages(); + clearInvocations(mPhone); + + // Verify the override policy doesn't take effect because the DDS is user disabled. + mDataSettingsManagerUT.setMobileDataPolicy( + TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true); + processAllMessages(); + verify(mPhone, never()).notifyDataEnabled(anyBoolean(), anyInt()); + + // Verify the override takes effect upon DDS user enabled. + doReturn(true).when(phone2).isUserDataEnabled(); + callback.onUserDataEnabledChanged(true, "callingPackage"); + verify(mPhone).notifyDataEnabled(true, TelephonyManager.DATA_ENABLED_REASON_OVERRIDE); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java index 35d3b9268a..22cdaae64f 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java @@ -18,6 +18,7 @@ package com.android.internal.telephony.data; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNotNull; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; @@ -25,9 +26,16 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.content.Intent; +import android.database.ContentObserver; import android.net.NetworkAgent; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; import android.telephony.Annotation.ValidationStatus; import android.telephony.CarrierConfigManager; +import android.telephony.TelephonyManager; +import android.test.mock.MockContentResolver; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -42,21 +50,53 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class DataStallRecoveryManagerTest extends TelephonyTest { + private FakeContentResolver mFakeContentResolver; + // Mocked classes private DataStallRecoveryManagerCallback mDataStallRecoveryManagerCallback; private DataStallRecoveryManager mDataStallRecoveryManager; + /** + * The fake content resolver used to receive change event from global settings + * and notify observer of a change in content in DataStallRecoveryManager + */ + private class FakeContentResolver extends MockContentResolver { + @Override + public void notifyChange(Uri uri, ContentObserver observer) { + super.notifyChange(uri, observer); + logd("onChanged(uri=" + uri + ")" + observer); + if (observer != null) { + observer.dispatchChange(false, uri); + } else { + mDataStallRecoveryManager.getContentObserver().dispatchChange(false, uri); + } + } + } + @Before public void setUp() throws Exception { logd("DataStallRecoveryManagerTest +Setup!"); super.setUp(getClass().getSimpleName()); + Field field = DataStallRecoveryManager.class.getDeclaredField("mPredictWaitingMillis"); + field.setAccessible(true); + + mFakeContentResolver = new FakeContentResolver(); + doReturn(mFakeContentResolver).when(mContext).getContentResolver(); + // Set the global settings for action enabled state and duration to + // the default test values. + Settings.Global.putString(mFakeContentResolver, Settings.Global.DSRM_DURATION_MILLIS, + "100,100,100,100,0"); + Settings.Global.putString(mFakeContentResolver, Settings.Global.DSRM_ENABLED_ACTIONS, + "true,true,false,true,true"); + mDataStallRecoveryManagerCallback = mock(DataStallRecoveryManagerCallback.class); mCarrierConfigManager = mPhone.getContext().getSystemService(CarrierConfigManager.class); long[] dataStallRecoveryTimersArray = new long[] {100, 100, 100, 100}; @@ -81,11 +121,15 @@ public class DataStallRecoveryManagerTest extends TelephonyTest { mMockedWwanDataServiceManager, mTestableLooper.getLooper(), mDataStallRecoveryManagerCallback); + + field.set(mDataStallRecoveryManager, 0L); + logd("DataStallRecoveryManagerTest -Setup!"); } @After public void tearDown() throws Exception { + mFakeContentResolver = null; mDataStallRecoveryManager = null; super.tearDown(); } @@ -93,22 +137,22 @@ public class DataStallRecoveryManagerTest extends TelephonyTest { private void sendValidationStatusCallback(@ValidationStatus int status) { ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor = ArgumentCaptor.forClass(DataNetworkControllerCallback.class); - verify(mDataNetworkController) + verify(mDataNetworkController, times(2)) .registerDataNetworkControllerCallback( dataNetworkControllerCallbackCaptor.capture()); DataNetworkControllerCallback dataNetworkControllerCallback = - dataNetworkControllerCallbackCaptor.getValue(); + dataNetworkControllerCallbackCaptor.getAllValues().get(0); dataNetworkControllerCallback.onInternetDataNetworkValidationStatusChanged(status); } private void sendOnInternetDataNetworkCallback(boolean isConnected) { ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor = ArgumentCaptor.forClass(DataNetworkControllerCallback.class); - verify(mDataNetworkController) + verify(mDataNetworkController, times(2)) .registerDataNetworkControllerCallback( dataNetworkControllerCallbackCaptor.capture()); DataNetworkControllerCallback dataNetworkControllerCallback = - dataNetworkControllerCallbackCaptor.getValue(); + dataNetworkControllerCallbackCaptor.getAllValues().get(0); if (isConnected) { List<DataNetwork> dataprofile = new ArrayList<>(); @@ -363,4 +407,105 @@ public class DataStallRecoveryManagerTest extends TelephonyTest { } assertThat(mDataStallRecoveryManager.mDataStallStartMs != 0).isTrue(); } + + /** + * Tests the DSRM process to send three intents for three action changes. + */ + @Test + public void testSendDSRMData() throws Exception { + ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class); + + logd("Set phone status to normal status."); + sendOnInternetDataNetworkCallback(true); + doReturn(mSignalStrength).when(mPhone).getSignalStrength(); + doReturn(PhoneConstants.State.IDLE).when(mPhone).getState(); + + // Set the expected behavior of the DataStallRecoveryManager. + logd("Start DSRM process, set action to 1"); + mDataStallRecoveryManager.setRecoveryAction(1); + logd("Sending validation failed callback"); + sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID); + processAllFutureMessages(); + + logd("Verify that the DataStallRecoveryManager sends the expected intents."); + verify(mPhone.getContext(), times(3)).sendBroadcast(captorIntent.capture()); + logd(captorIntent.getAllValues().toString()); + for (int i = 0; i < captorIntent.getAllValues().size(); i++) { + Intent intent = captorIntent.getAllValues().get(i); + // Check and assert if intent is null + assertNotNull(intent); + // Check and assert if intent is not ACTION_DATA_STALL_DETECTED + assertThat(intent.getAction()).isEqualTo( + TelephonyManager.ACTION_DATA_STALL_DETECTED); + // Get the extra data + Bundle bundle = (Bundle) intent.getExtra("EXTRA_DSRS_STATS_BUNDLE"); + // Check and assert if bundle is null + assertNotNull(bundle); + // Dump bundle data + logd(bundle.toString()); + int size = bundle.size(); + logd("bundle size is " + size); + // Check if bundle size is 19 + assertThat(size).isEqualTo(19); + } + } + + /** + * Tests update action enable state and duration from global settings. + */ + @Test + public void testUpdateGlobalSettings() throws Exception { + Field field = DataStallRecoveryManager.class.getDeclaredField("mPredictWaitingMillis"); + field.setAccessible(true); + + // Set duration to 10000/20000/30000/40000 + Settings.Global.putString( + mFakeContentResolver, Settings.Global.DSRM_DURATION_MILLIS, + "10000,20000,30000,40000,0"); + // Send onChange event with Settings.Global.DSRM_DURATION_MILLIS to fake ContentResolver + mFakeContentResolver.notifyChange( + Settings.Global.getUriFor(Settings.Global.DSRM_DURATION_MILLIS), null); + processAllFutureMessages(); + // Verify that the durations are correct values. + assertThat(mDataStallRecoveryManager.getDataStallRecoveryDelayMillis(0)).isEqualTo(10000L); + assertThat(mDataStallRecoveryManager.getDataStallRecoveryDelayMillis(1)).isEqualTo(20000L); + assertThat(mDataStallRecoveryManager.getDataStallRecoveryDelayMillis(2)).isEqualTo(30000L); + assertThat(mDataStallRecoveryManager.getDataStallRecoveryDelayMillis(3)).isEqualTo(40000L); + + // Set action enable state to true/false/false/false/true + Settings.Global.putString( + mFakeContentResolver, Settings.Global.DSRM_ENABLED_ACTIONS, + "true,false,false,false,true"); + // Send onChange event with Settings.Global.DSRM_ENABLED_ACTIONS to fake ContentResolver + mFakeContentResolver.notifyChange( + Settings.Global.getUriFor(Settings.Global.DSRM_ENABLED_ACTIONS), null); + processAllFutureMessages(); + // Verify that the action enable state are correct values. + assertThat(mDataStallRecoveryManager.shouldSkipRecoveryAction(0)).isEqualTo(false); + assertThat(mDataStallRecoveryManager.shouldSkipRecoveryAction(1)).isEqualTo(true); + assertThat(mDataStallRecoveryManager.shouldSkipRecoveryAction(2)).isEqualTo(true); + assertThat(mDataStallRecoveryManager.shouldSkipRecoveryAction(3)).isEqualTo(true); + assertThat(mDataStallRecoveryManager.shouldSkipRecoveryAction(4)).isEqualTo(false); + // Check the predict waiting millis + assertThat(field.get(mDataStallRecoveryManager)).isEqualTo(1000L); + // Test predict waiting millis to rollback to 0 if there is no global duration and action + // Set duration to empty + Settings.Global.putString( + mFakeContentResolver, Settings.Global.DSRM_DURATION_MILLIS, + ""); + // Send onChange event with Settings.Global.DSRM_DURATION_MILLIS to fake ContentResolver + mFakeContentResolver.notifyChange( + Settings.Global.getUriFor(Settings.Global.DSRM_DURATION_MILLIS), null); + processAllFutureMessages(); + // Set action to empty + Settings.Global.putString( + mFakeContentResolver, Settings.Global.DSRM_ENABLED_ACTIONS, + ""); + // Send onChange event with Settings.Global.DSRM_ENABLED_ACTIONS to fake ContentResolver + mFakeContentResolver.notifyChange( + Settings.Global.getUriFor(Settings.Global.DSRM_ENABLED_ACTIONS), null); + processAllFutureMessages(); + // Check if predict waiting millis is 0 + assertThat(field.get(mDataStallRecoveryManager)).isEqualTo(0L); + } } diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java index 0eba3fe96f..c215483b63 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java @@ -26,9 +26,8 @@ import static android.telephony.TelephonyManager.SIM_STATE_LOADED; import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM; import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN; import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE; - +import static com.android.internal.telephony.data.AutoDataSwitchController.EVALUATION_REASON_VOICE_CALL_END; import static com.android.internal.telephony.data.PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -65,6 +64,8 @@ import android.telephony.PhoneCapability; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyDisplayInfo; +import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -118,11 +119,13 @@ public class PhoneSwitcherTest extends TelephonyTest { private GsmCdmaCall mInactiveCall; private GsmCdmaCall mDialCall; private GsmCdmaCall mIncomingCall; + private GsmCdmaCall mAlertingCall; private ISetOpportunisticDataCallback mSetOpptDataCallback1; private ISetOpportunisticDataCallback mSetOpptDataCallback2; PhoneSwitcher.ImsRegTechProvider mMockImsRegTechProvider; private SubscriptionInfo mSubscriptionInfo; private ISub mMockedIsub; + private AutoDataSwitchController mAutoDataSwitchController; private PhoneSwitcher mPhoneSwitcherUT; private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener; @@ -137,6 +140,10 @@ public class PhoneSwitcherTest extends TelephonyTest { private int mActiveModemCount = 2; private int mSupportedModemCount = 2; private int mMaxDataAttachModemCount = 1; + private AutoDataSwitchController.AutoDataSwitchControllerCallback mAutoDataSwitchCallback; + private TelephonyDisplayInfo mTelephonyDisplayInfo = new TelephonyDisplayInfo( + TelephonyManager.NETWORK_TYPE_NR, + TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false); @Before public void setUp() throws Exception { @@ -153,11 +160,13 @@ public class PhoneSwitcherTest extends TelephonyTest { mInactiveCall = mock(GsmCdmaCall.class); mDialCall = mock(GsmCdmaCall.class); mIncomingCall = mock(GsmCdmaCall.class); + mAlertingCall = mock(GsmCdmaCall.class); mSetOpptDataCallback1 = mock(ISetOpportunisticDataCallback.class); mSetOpptDataCallback2 = mock(ISetOpportunisticDataCallback.class); mMockImsRegTechProvider = mock(PhoneSwitcher.ImsRegTechProvider.class); mSubscriptionInfo = mock(SubscriptionInfo.class); mMockedIsub = mock(ISub.class); + mAutoDataSwitchController = mock(AutoDataSwitchController.class); PhoneCapability phoneCapability = new PhoneCapability(1, 1, null, false, new int[0]); doReturn(phoneCapability).when(mPhoneConfigurationManager).getCurrentPhoneCapability(); @@ -167,6 +176,7 @@ public class PhoneSwitcherTest extends TelephonyTest { doReturn(Call.State.HOLDING).when(mHoldingCall).getState(); doReturn(Call.State.DIALING).when(mDialCall).getState(); doReturn(Call.State.INCOMING).when(mIncomingCall).getState(); + doReturn(Call.State.ALERTING).when(mAlertingCall).getState(); doReturn(true).when(mInactiveCall).isIdle(); doReturn(false).when(mActiveCall).isIdle(); @@ -179,6 +189,8 @@ public class PhoneSwitcherTest extends TelephonyTest { doReturn(mMockedIsub).when(mIBinder).queryLocalInterface(anyString()); doReturn(mPhone).when(mPhone).getImsPhone(); mServiceManagerMockedServices.put("isub", mIBinder); + + doReturn(mTelephonyDisplayInfo).when(mDisplayInfoController).getTelephonyDisplayInfo(); } @After @@ -187,6 +199,7 @@ public class PhoneSwitcherTest extends TelephonyTest { mSubChangedListener = null; mConnectivityManager = null; mNetworkProviderMessenger = null; + mTelephonyDisplayInfo = null; super.tearDown(); } @@ -374,22 +387,8 @@ public class PhoneSwitcherTest extends TelephonyTest { } /** Test Data Auto Switch **/ - - /** - * Trigger conditions - * 1. service state changes - * 2. data setting changes - * - user toggle data - * - user toggle auto switch feature - * 3. default network changes - * - current network lost - * - network become active on non-cellular network - * 4. subscription changes - * - slot/sub mapping changes - */ @Test - @SmallTest - public void testAutoDataSwitchCancelScenario_onPrimary() throws Exception { + public void testAutoDataSwitch_retry() throws Exception { initialize(); // Phone 0 has sub 1, phone 1 has sub 2. // Sub 1 is default data sub. @@ -397,142 +396,28 @@ public class PhoneSwitcherTest extends TelephonyTest { setSlotIndexToSubId(1, 2); setDefaultDataSubId(1); - // 0. When all conditions met - prepareIdealAutoSwitchCondition(); + mAutoDataSwitchCallback.onRequireValidation(1/*Phone2*/, true); processAllFutureMessages(); - // Verify attempting to switch + // Mock validation failed, expect retry attempt verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false), eq(mPhoneSwitcherUT.mValidationCallback)); - doReturn(true).when(mCellularNetworkValidator).isValidating(); - - // 1. Service state becomes not ideal - primary is available again - clearInvocations(mCellularNetworkValidator); - serviceStateChanged(0, NetworkRegistrationInfo.REGISTRATION_STATE_HOME); - processAllFutureMessages(); - - verify(mCellularNetworkValidator).stopValidation(); - - // 2.1 User data disabled on primary SIM - prepareIdealAutoSwitchCondition(); - processAllFutureMessages(); - clearInvocations(mCellularNetworkValidator); - doReturn(false).when(mPhone).isUserDataEnabled(); - mDataSettingsManagerCallbacks.get(0).onDataEnabledChanged(false, 123 , ""); - processAllFutureMessages(); - - verify(mCellularNetworkValidator).stopValidation(); - - // 2.2 Auto switch feature is disabled - prepareIdealAutoSwitchCondition(); - processAllFutureMessages(); - clearInvocations(mCellularNetworkValidator); - doReturn(false).when(mPhone2).isDataAllowed(); - mDataSettingsManagerCallbacks.get(1).onDataEnabledChanged(false, 123 , ""); - processAllFutureMessages(); - - verify(mCellularNetworkValidator).stopValidation(); - - // 3.1 No default network - prepareIdealAutoSwitchCondition(); - processAllFutureMessages(); - clearInvocations(mCellularNetworkValidator); - doReturn(new NetworkCapabilities() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)) - .when(mConnectivityManager).getNetworkCapabilities(any()); - mPhoneSwitcherUT.sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH); - processAllFutureMessages(); - - verify(mCellularNetworkValidator).stopValidation(); - } - - public void testAutoSwitchToSecondarySucceed() { - prepareIdealAutoSwitchCondition(); - processAllFutureMessages(); - mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 2); + mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 2/*Phone2*/); processAllMessages(); - // Confirm auto switched to secondary sub Id 1/phone 0 - assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId()); - } - - @Test - @SmallTest - public void testAutoDataSwitch_switchBackToPrimary() throws Exception { - initialize(); - // Phone 0 has sub 1, phone 1 has sub 2. - // Sub 1 is default data sub. - setSlotIndexToSubId(0, 1); - setSlotIndexToSubId(1, 2); - setDefaultDataSubId(1); - - testAutoSwitchToSecondarySucceed(); - // 1.1 service state changes - primary becomes available, need validation pass to switch - serviceStateChanged(0, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING); - processAllFutureMessages(); - verify(mCellularNetworkValidator).validate(eq(1), anyLong(), eq(false), - eq(mPhoneSwitcherUT.mValidationCallback)); - mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 1); - processAllMessages(); - - assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId()); // since validation failed - - serviceStateChanged(0, NetworkRegistrationInfo.REGISTRATION_STATE_HOME); - processAllFutureMessages(); - mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 1); - processAllMessages(); - - assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // since validation passed - - testAutoSwitchToSecondarySucceed(); - // 1.2 service state changes - secondary becomes unavailable, NO need validation - // The later validation requirement overrides the previous - serviceStateChanged(0, NetworkRegistrationInfo.REGISTRATION_STATE_HOME/*need validate*/); - serviceStateChanged(1, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING/*no need*/); - processAllFutureMessages(); - mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 1); - processAllMessages(); - - assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // since no need validation - testAutoSwitchToSecondarySucceed(); - // 2.1 User data disabled on primary SIM - clearInvocations(mCellularNetworkValidator); - doReturn(false).when(mPhone).isUserDataEnabled(); - mDataSettingsManagerCallbacks.get(0).onDataEnabledChanged(false, 123 , ""); - processAllFutureMessages(); + verify(mAutoDataSwitchController).evaluateRetryOnValidationFailed(); - assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // since no need validation - verify(mCellularNetworkValidator, never()).validate(eq(1), anyLong(), eq(false), - eq(mPhoneSwitcherUT.mValidationCallback)); - - testAutoSwitchToSecondarySucceed(); - // 2.2 Auto switch feature is disabled - clearInvocations(mCellularNetworkValidator); - doReturn(false).when(mPhone2).isDataAllowed(); - mDataSettingsManagerCallbacks.get(0).onDataEnabledChanged(false, 123 , ""); - processAllFutureMessages(); - - assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // since no need validation - verify(mCellularNetworkValidator, never()).validate(eq(1), anyLong(), eq(false), - eq(mPhoneSwitcherUT.mValidationCallback)); - - testAutoSwitchToSecondarySucceed(); - // 3.1 Default network is active on non-cellular transport - clearInvocations(mCellularNetworkValidator); - doReturn(new NetworkCapabilities() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)) - .when(mConnectivityManager).getNetworkCapabilities(any()); - mPhoneSwitcherUT.sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH); + // Test clear failed count upon switch succeeded. + mAutoDataSwitchCallback.onRequireValidation(1/*Phone2*/, true); processAllFutureMessages(); - mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 1); + mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 2/*Phone2*/); processAllMessages(); - assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // since no need validation + verify(mAutoDataSwitchController).resetFailedCount(); } @Test - @SmallTest - public void testAutoDataSwitchCancel_onSecondary() throws Exception { + public void testAutoDataSwitch_setNotification() throws Exception { initialize(); // Phone 0 has sub 1, phone 1 has sub 2. // Sub 1 is default data sub. @@ -540,121 +425,50 @@ public class PhoneSwitcherTest extends TelephonyTest { setSlotIndexToSubId(1, 2); setDefaultDataSubId(1); - testAutoSwitchToSecondarySucceed(); - clearInvocations(mCellularNetworkValidator); - // attempts the switch back due to secondary becomes ROAMING - serviceStateChanged(1, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING); - processAllFutureMessages(); - - verify(mCellularNetworkValidator).validate(eq(1), anyLong(), eq(false), - eq(mPhoneSwitcherUT.mValidationCallback)); - doReturn(true).when(mCellularNetworkValidator).isValidating(); - - // cancel the switch back attempt due to secondary back to HOME - serviceStateChanged(1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME); + // Verify no notification check if switch failed. + Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1/*phoneId*/, + null, new Throwable())).sendToTarget(); processAllMessages(); - mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 1); - processAllMessages(); - - assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId()); - verify(mCellularNetworkValidator).stopValidation(); - } + verify(mAutoDataSwitchController, never()).displayAutoDataSwitchNotification( + anyInt(), anyBoolean()); - @Test - @SmallTest - public void testAutoDataSwitch_retry() throws Exception { - initialize(); - // Phone 0 has sub 1, phone 1 has sub 2. - // Sub 1 is default data sub. - setSlotIndexToSubId(0, 1); - setSlotIndexToSubId(1, 2); - setDefaultDataSubId(1); - - prepareIdealAutoSwitchCondition(); - processAllFutureMessages(); - - // Verify attempting to switch - verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false), - eq(mPhoneSwitcherUT.mValidationCallback)); - mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 2); + // Verify for switch not due to auto + Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1/*phoneId*/, + null, null)).sendToTarget(); processAllMessages(); + verify(mAutoDataSwitchController).displayAutoDataSwitchNotification(1/*phoneId*/, false); - assertTrue(mPhoneSwitcherUT.hasMessages(EVENT_EVALUATE_AUTO_SWITCH)); - - processAllFutureMessages(); - mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 2); - processAllFutureMessages(); - - assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId()); - } - - @Test - @SmallTest - public void testAutoDataSwitch_setNotification() throws Exception { - SubscriptionInfo mockedInfo = mock(SubscriptionInfo.class); - doReturn(false).when(mockedInfo).isOpportunistic(); - doReturn(mockedInfo).when(mSubscriptionManagerService).getSubscriptionInfo(anyInt()); - initialize(); - // Phone 0 has sub 1, phone 1 has sub 2. - // Sub 1 is default data sub. - setSlotIndexToSubId(0, 1); - setSlotIndexToSubId(1, 2); - setDefaultDataSubId(1); - - testAutoSwitchToSecondarySucceed(); - clearInvocations(mSubscriptionManagerService); - Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1, null, null)) - .sendToTarget(); + // Verify for switch due to auto + mAutoDataSwitchCallback.onRequireValidation(1/*Phone2*/, false); processAllMessages(); - verify(mSubscriptionManagerService).getSubscriptionInfo(2); - // switch back to primary - clearInvocations(mSubscriptionManagerService); - Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(0, null, null)) - .sendToTarget(); + mPhoneSwitcherUT.mValidationCallback.onValidationDone(true, 2/*Phone2*/); + Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1/*phoneId*/, + null, null)).sendToTarget(); processAllMessages(); - verify(mSubscriptionManagerService, never()).getSubscriptionInfo(1); - Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1, null, null)) - .sendToTarget(); - processAllMessages(); - verify(mSubscriptionManagerService, never()).getSubscriptionInfo(2); + verify(mAutoDataSwitchController).displayAutoDataSwitchNotification(1/*phoneId*/, true); } @Test @SmallTest public void testAutoDataSwitch_exemptPingTest() throws Exception { initialize(); - // Change resource overlay - doReturn(false).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired(); - mPhoneSwitcherUT = new PhoneSwitcher(mMaxDataAttachModemCount, mContext, Looper.myLooper()); - processAllMessages(); // Phone 0 has sub 1, phone 1 has sub 2. // Sub 1 is default data sub. setSlotIndexToSubId(0, 1); setSlotIndexToSubId(1, 2); - setDefaultDataSubId(1); - //1. Attempting to switch to nDDS, switch even if validation failed - prepareIdealAutoSwitchCondition(); + //Attempting to switch to nDDS, switch even if validation failed + mAutoDataSwitchCallback.onRequireValidation(1/*Phone2*/, false); processAllFutureMessages(); verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false), eq(mPhoneSwitcherUT.mValidationCallback)); - mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 2); + mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 2/*Phone2*/); processAllMessages(); assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId()); // switch succeeds - - //2. Attempting to switch back to DDS, switch even if validation failed - serviceStateChanged(0, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING); - processAllFutureMessages(); - verify(mCellularNetworkValidator).validate(eq(1), anyLong(), eq(false), - eq(mPhoneSwitcherUT.mValidationCallback)); - mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 1); - processAllMessages(); - - assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // switch succeeds } /** @@ -1096,13 +910,19 @@ public class PhoneSwitcherTest extends TelephonyTest { // Phone 0 should be the default data phoneId. assertEquals(0, mPhoneSwitcherUT.getPreferredDataPhoneId()); - // Phone2 has active IMS call on LTE. And data of DEFAULT apn is enabled. This should - // trigger data switch. + // Dialing shouldn't trigger switch because we give modem time to deal with the dialing call + // first. Phone2 has active IMS call on LTE. And data of DEFAULT apn is enabled. doReturn(mImsPhone).when(mPhone2).getImsPhone(); doReturn(true).when(mPhone2).isDataAllowed(); mockImsRegTech(1, REGISTRATION_TECH_LTE); notifyPhoneAsInDial(mImsPhone); + // Phone1 should remain as the preferred data phone + assertEquals(0, mPhoneSwitcherUT.getPreferredDataPhoneId()); + + // Dialing -> Alert, should trigger phone switch + notifyPhoneAsAlerting(mImsPhone); + // Phone2 should be preferred data phone assertEquals(1, mPhoneSwitcherUT.getPreferredDataPhoneId()); } @@ -1200,7 +1020,6 @@ public class PhoneSwitcherTest extends TelephonyTest { } @Test - @SmallTest public void testNonDefaultDataPhoneInCall() throws Exception { doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported(); initialize(); @@ -1250,11 +1069,9 @@ public class PhoneSwitcherTest extends TelephonyTest { // Phone(DDS) call ended. // Honor auto data switch's suggestion: if DDS is OOS, auto switch to Phone2(nDDS). - serviceStateChanged(1, NetworkRegistrationInfo.REGISTRATION_STATE_HOME); - serviceStateChanged(0, NetworkRegistrationInfo - .REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING); - doReturn(null).when(mConnectivityManager).getNetworkCapabilities(any()); notifyPhoneAsInactive(mPhone); + verify(mAutoDataSwitchController).evaluateAutoDataSwitch(EVALUATION_REASON_VOICE_CALL_END); + mAutoDataSwitchCallback.onRequireValidation(1 /*Phone2*/, true); // verify immediately switch back to DDS upon call ends verify(mMockRadioConfig).setPreferredDataModem(eq(0), any()); @@ -1930,6 +1747,12 @@ public class PhoneSwitcherTest extends TelephonyTest { processAllMessages(); } + private void notifyPhoneAsAlerting(Phone phone) { + doReturn(mAlertingCall).when(phone).getForegroundCall(); + mPhoneSwitcherUT.sendEmptyMessage(EVENT_PRECISE_CALL_STATE_CHANGED); + processAllMessages(); + } + private void notifyPhoneAsInIncomingCall(Phone phone) { doReturn(mIncomingCall).when(phone).getForegroundCall(); mPhoneSwitcherUT.sendEmptyMessage(EVENT_PRECISE_CALL_STATE_CHANGED); @@ -2042,6 +1865,13 @@ public class PhoneSwitcherTest extends TelephonyTest { (Map<Integer, DataSettingsManager.DataSettingsManagerCallback>) field.get(mPhoneSwitcherUT); + field = PhoneSwitcher.class.getDeclaredField("mAutoDataSwitchCallback"); + field.setAccessible(true); + mAutoDataSwitchCallback = (AutoDataSwitchController.AutoDataSwitchControllerCallback) + field.get(mPhoneSwitcherUT); + + replaceInstance(PhoneSwitcher.class, "mAutoDataSwitchController", mPhoneSwitcherUT, + mAutoDataSwitchController); processAllMessages(); verify(mTelephonyRegistryManager).addOnSubscriptionsChangedListener(any(), any()); @@ -2058,6 +1888,9 @@ public class PhoneSwitcherTest extends TelephonyTest { doReturn(true).when(mPhone2).isUserDataEnabled(); doReturn(mDataSettingsManager2).when(mPhone2).getDataSettingsManager(); doReturn(mSST2).when(mPhone2).getServiceStateTracker(); + doReturn(mDisplayInfoController).when(mPhone2).getDisplayInfoController(); + doReturn(mSignalStrengthController).when(mPhone2).getSignalStrengthController(); + doReturn(mSignalStrength).when(mPhone2).getSignalStrength(); for (int i = 0; i < supportedModemCount; i++) { mSlotIndexToSubId[i] = new int[1]; mSlotIndexToSubId[i][0] = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -2192,12 +2025,9 @@ public class PhoneSwitcherTest extends TelephonyTest { private void setDefaultDataSubId(int defaultDataSub) { mDefaultDataSub = defaultDataSub; doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId(); - if (defaultDataSub == 1) { - doReturn(true).when(mPhone).isUserDataEnabled(); - doReturn(false).when(mPhone2).isUserDataEnabled(); - } else { - doReturn(false).when(mPhone).isUserDataEnabled(); - doReturn(true).when(mPhone2).isUserDataEnabled(); + for (Phone phone : mPhones) { + doAnswer(invocation -> phone.getSubId() == mDefaultDataSub) + .when(phone).isUserDataEnabled(); } sendDefaultDataSubChanged(); } diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java index b74c8af2dc..cd9d9ad1b1 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java @@ -47,6 +47,7 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.nullable; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -1049,6 +1050,67 @@ public class ImsPhoneTest extends TelephonyTest { mContextFixture.addCallingOrSelfPermission(""); } + @Test + @SmallTest + public void testSetPhoneNumberForSourceIgnoreGlobalPhoneNumberFormat() { + // In reality the method under test runs in phone process so has MODIFY_PHONE_STATE + mContextFixture.addCallingOrSelfPermission(MODIFY_PHONE_STATE); + int subId = 1; + doReturn(subId).when(mPhone).getSubId(); + doReturn(new SubscriptionInfoInternal.Builder().setId(subId).setSimSlotIndex(0) + .setCountryIso("gb").build()).when(mSubscriptionManagerService) + .getSubscriptionInfoInternal(subId); + // Set carrier config to ignore global phone number format + PersistableBundle bundle = new PersistableBundle(); + bundle.putBoolean( + CarrierConfigManager.Ims.KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL, true); + doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(eq(subId), + eq(CarrierConfigManager.Ims.KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL)); + + // 1. Two non-global phone number; 1st is set. + Uri[] associatedUris = new Uri[] { + Uri.parse("sip:01012345678@lte-uplus.co.kr"), + Uri.parse("tel:01012345678") + }; + mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); + + verify(mSubscriptionManagerService).setNumberFromIms(subId, "01012345678"); + + // 2. 1st non-global phone number and 2nd global number; 2nd is set. + associatedUris = new Uri[] { + Uri.parse("sip:01012345678@lte-uplus.co.kr"), + Uri.parse("tel:+821012345678") + }; + mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); + + verify(mSubscriptionManagerService).setNumberFromIms(subId, "+821012345678"); + + // 3. 1st sip-uri is not phone number and 2nd valid: 2nd is set. + associatedUris = new Uri[] { + Uri.parse("sip:john.doe@ims.x.com"), + Uri.parse("sip:01022223333@lte-uplus.co.kr"), + Uri.parse("tel:01022223333") + }; + mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); + + verify(mSubscriptionManagerService).setNumberFromIms(subId, "01022223333"); + + clearInvocations(mSubscriptionManagerService); + + // 4. Invalid phone number : not set. + associatedUris = new Uri[] { + Uri.parse("sip:john.doe@ims.x.com"), + Uri.parse("sip:hone3333@lte-uplus.co.kr"), + Uri.parse("tel:abcd1234555") + }; + mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); + + verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString()); + + // Clean up + mContextFixture.addCallingOrSelfPermission(""); + } + /** * Verifies that valid radio technology is passed to RIL * when IMS registration state changes to registered. diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java index 7b66a52e40..f186f987f2 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java @@ -30,6 +30,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -152,6 +153,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -324,6 +326,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -336,6 +339,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -373,6 +377,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.dataRat); @@ -385,6 +390,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -416,6 +422,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -457,6 +464,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -495,6 +503,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = serviceStateCaptor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -507,6 +516,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0); assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, serviceSwitch.ratFrom); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratTo); @@ -548,6 +558,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -559,6 +570,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(CARRIER1_ID, state.carrierId); assertEquals(100L, state.totalTimeMillis); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -588,6 +600,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(0L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -629,6 +642,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -641,6 +655,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = captor.getAllValues().get(2); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -653,6 +668,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(400L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = captor.getAllValues().get(3); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -665,6 +681,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(800L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -710,6 +727,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.dataRat); @@ -722,6 +740,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(5000L, state.totalTimeMillis); assertEquals(true, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = captor.getAllValues().get(2); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -734,6 +753,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -750,11 +770,31 @@ public class ServiceStateStatsTest extends TelephonyTest { mockWwanPsRat(TelephonyManager.NETWORK_TYPE_UMTS); mockWwanCsRat(TelephonyManager.NETWORK_TYPE_UMTS); doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mImsStats).getImsVoiceRadioTech(); - doReturn(ServiceState.ROAMING_TYPE_INTERNATIONAL).when(mServiceState).getVoiceRoamingType(); + NetworkRegistrationInfo voiceNri = new NetworkRegistrationInfo.Builder() + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS) + // This sets mNetworkRegistrationState + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) + .build(); + voiceNri.setRoamingType(ServiceState.ROAMING_TYPE_INTERNATIONAL); + doReturn(voiceNri) + .when(mServiceState) + .getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_CS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); mServiceStateStats.onServiceStateChanged(mServiceState); mServiceStateStats.incTimeMillis(200L); // Voice and data roaming - doReturn(ServiceState.ROAMING_TYPE_INTERNATIONAL).when(mServiceState).getDataRoamingType(); + NetworkRegistrationInfo dataNri = new NetworkRegistrationInfo.Builder() + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS) + // This sets mNetworkRegistrationState + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) + .build(); + dataNri.setRoamingType(ServiceState.ROAMING_TYPE_INTERNATIONAL); + doReturn(dataNri) + .when(mServiceState) + .getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); mServiceStateStats.onServiceStateChanged(mServiceState); mServiceStateStats.incTimeMillis(400L); @@ -779,6 +819,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = serviceStateCaptor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat); @@ -791,6 +832,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = serviceStateCaptor.getAllValues().get(2); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat); @@ -803,6 +845,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(400L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratFrom); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, serviceSwitch.ratTo); @@ -817,6 +860,76 @@ public class ServiceStateStatsTest extends TelephonyTest { @Test @SmallTest + public void onServiceStateChanged_roamingWithOverride() throws Exception { + // Using default service state for LTE + + mServiceStateStats.onServiceStateChanged(mServiceState); + mServiceStateStats.incTimeMillis(100L); + // Voice roaming + doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getVoiceNetworkType(); + doReturn(TelephonyManager.NETWORK_TYPE_UMTS).when(mServiceState).getDataNetworkType(); + NetworkRegistrationInfo roamingNri = new NetworkRegistrationInfo.Builder() + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS) + // This sets mNetworkRegistrationState + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) + .build(); + roamingNri.setRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING); + doReturn(roamingNri).when(mServiceState) + .getNetworkRegistrationInfo( + anyInt(), eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)); + doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mImsStats).getImsVoiceRadioTech(); + mServiceStateStats.onServiceStateChanged(mServiceState); + mServiceStateStats.incTimeMillis(400L); + + // There should be 2 service states and 1 data service switch (LTE to UMTS) + mServiceStateStats.conclude(); + ArgumentCaptor<CellularServiceState> serviceStateCaptor = + ArgumentCaptor.forClass(CellularServiceState.class); + ArgumentCaptor<CellularDataServiceSwitch> serviceSwitchCaptor = + ArgumentCaptor.forClass(CellularDataServiceSwitch.class); + verify(mPersistAtomsStorage, times(2)) + .addCellularServiceStateAndCellularDataServiceSwitch( + serviceStateCaptor.capture(), serviceSwitchCaptor.capture()); + CellularServiceState state = serviceStateCaptor.getAllValues().get(0); + assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); + assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); + assertEquals(ServiceState.ROAMING_TYPE_NOT_ROAMING, state.voiceRoamingType); + assertEquals(ServiceState.ROAMING_TYPE_NOT_ROAMING, state.dataRoamingType); + assertFalse(state.isEndc); + assertEquals(0, state.simSlotIndex); + assertFalse(state.isMultiSim); + assertEquals(CARRIER1_ID, state.carrierId); + assertEquals(100L, state.totalTimeMillis); + assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); + state = serviceStateCaptor.getAllValues().get(1); + assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat); + assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat); + // Atom should show roaming, despite type being unknown + assertEquals(ServiceState.ROAMING_TYPE_UNKNOWN, state.voiceRoamingType); + assertEquals(ServiceState.ROAMING_TYPE_UNKNOWN, state.dataRoamingType); + assertFalse(state.isEndc); + assertEquals(0, state.simSlotIndex); + assertFalse(state.isMultiSim); + assertEquals(CARRIER1_ID, state.carrierId); + assertEquals(400L, state.totalTimeMillis); + assertEquals(false, state.isEmergencyOnly); + assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); + CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0); + assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratFrom); + assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, serviceSwitch.ratTo); + assertEquals(0, serviceSwitch.simSlotIndex); + assertFalse(serviceSwitch.isMultiSim); + assertEquals(CARRIER1_ID, serviceSwitch.carrierId); + assertEquals(1, serviceSwitch.switchCount); + assertNull(serviceSwitchCaptor.getAllValues().get(1)); // produced by conclude() + verifyNoMoreInteractions(mPersistAtomsStorage); + } + + @Test + @SmallTest public void onServiceStateChanged_dualSim() throws Exception { // Using default service state for LTE // Only difference between the 2 slots is slot index @@ -860,6 +973,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = serviceStateCaptor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -872,6 +986,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = serviceStateCaptor.getAllValues().get(2); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat); @@ -884,6 +999,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = serviceStateCaptor.getAllValues().get(3); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat); @@ -896,6 +1012,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratFrom); assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, serviceSwitch.ratTo); @@ -957,6 +1074,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(100L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); state = captor.getAllValues().get(1); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat); assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat); @@ -969,6 +1087,7 @@ public class ServiceStateStatsTest extends TelephonyTest { assertEquals(200L, state.totalTimeMillis); assertEquals(false, state.isEmergencyOnly); assertEquals(true, state.isInternetPdnUp); + assertEquals(true, state.isDataEnabled); verifyNoMoreInteractions(mPersistAtomsStorage); } @@ -1036,6 +1155,141 @@ public class ServiceStateStatsTest extends TelephonyTest { verifyNoMoreInteractions(mPersistAtomsStorage); } + @Test + @SmallTest + public void isNetworkRoaming_nullServiceState() throws Exception { + boolean result = ServiceStateStats.isNetworkRoaming(null); + + assertEquals(false, result); + } + + @Test + @SmallTest + public void isNetworkRoaming_notRoaming() throws Exception { + NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder() + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS) + // This sets mNetworkRegistrationState + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME) + .build(); + nri.setRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING); + doReturn(nri).when(mServiceState).getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + doReturn(nri).when(mServiceState).getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + + boolean result = ServiceStateStats.isNetworkRoaming(mServiceState); + boolean resultCs = ServiceStateStats.isNetworkRoaming( + mServiceState, NetworkRegistrationInfo.DOMAIN_CS); + boolean resultPs = ServiceStateStats.isNetworkRoaming( + mServiceState, NetworkRegistrationInfo.DOMAIN_PS); + + assertEquals(false, result); + assertEquals(false, resultCs); + assertEquals(false, resultPs); + } + + @Test + @SmallTest + public void isNetworkRoaming_csRoaming() throws Exception { + NetworkRegistrationInfo roamingNri = new NetworkRegistrationInfo.Builder() + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS) + // This sets mNetworkRegistrationState + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) + .build(); + roamingNri.setRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING); + doReturn(roamingNri).when(mServiceState).getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + + boolean result = ServiceStateStats.isNetworkRoaming(mServiceState); + boolean resultCs = ServiceStateStats.isNetworkRoaming( + mServiceState, NetworkRegistrationInfo.DOMAIN_CS); + boolean resultPs = ServiceStateStats.isNetworkRoaming( + mServiceState, NetworkRegistrationInfo.DOMAIN_PS); + + assertEquals(true, result); + assertEquals(true, resultCs); + assertEquals(false, resultPs); + } + + @Test + @SmallTest + public void isNetworkRoaming_psRoaming() throws Exception { + NetworkRegistrationInfo roamingNri = new NetworkRegistrationInfo.Builder() + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS) + // This sets mNetworkRegistrationState + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) + .build(); + roamingNri.setRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING); + doReturn(roamingNri).when(mServiceState).getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + + boolean result = ServiceStateStats.isNetworkRoaming(mServiceState); + boolean resultCs = ServiceStateStats.isNetworkRoaming( + mServiceState, NetworkRegistrationInfo.DOMAIN_CS); + boolean resultPs = ServiceStateStats.isNetworkRoaming( + mServiceState, NetworkRegistrationInfo.DOMAIN_PS); + + assertEquals(true, result); + assertEquals(false, resultCs); + assertEquals(true, resultPs); + } + + @Test + @SmallTest + public void isNetworkRoaming_bothRoaming() throws Exception { + NetworkRegistrationInfo roamingNri = new NetworkRegistrationInfo.Builder() + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS) + // This sets mNetworkRegistrationState + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) + .build(); + roamingNri.setRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING); + doReturn(roamingNri).when(mServiceState).getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + doReturn(roamingNri).when(mServiceState).getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + + boolean result = ServiceStateStats.isNetworkRoaming(mServiceState); + boolean resultCs = ServiceStateStats.isNetworkRoaming( + mServiceState, NetworkRegistrationInfo.DOMAIN_CS); + boolean resultPs = ServiceStateStats.isNetworkRoaming( + mServiceState, NetworkRegistrationInfo.DOMAIN_PS); + + assertEquals(true, result); + assertEquals(true, resultCs); + assertEquals(true, resultPs); + } + + @Test + @SmallTest + public void onVoiceServiceStateOverrideChanged_voiceCallingCapabilityChange() { + // Using default service state for LTE + mServiceStateStats.onServiceStateChanged(mServiceState); + mServiceStateStats.incTimeMillis(100L); + // Voice Calling registered + mServiceStateStats.onVoiceServiceStateOverrideChanged(true); + mServiceStateStats.incTimeMillis(100L); + // Voice Calling unregistered + mServiceStateStats.onVoiceServiceStateOverrideChanged(false); + mServiceStateStats.incTimeMillis(100L); + // Voice Calling unregistered again. Same state should not generate a new atom + mServiceStateStats.onVoiceServiceStateOverrideChanged(false); + mServiceStateStats.incTimeMillis(100L); + + // There should be 3 service state updates + mServiceStateStats.conclude(); + ArgumentCaptor<CellularServiceState> captor = + ArgumentCaptor.forClass(CellularServiceState.class); + verify(mPersistAtomsStorage, times(3)) + .addCellularServiceStateAndCellularDataServiceSwitch(captor.capture(), eq(null)); + CellularServiceState state = captor.getAllValues().get(0); + assertEquals(false, state.overrideVoiceService); + state = captor.getAllValues().get(1); + assertEquals(true, state.overrideVoiceService); + state = captor.getAllValues().get(2); + assertEquals(false, state.overrideVoiceService); + verifyNoMoreInteractions(mPersistAtomsStorage); + } + private void mockWwanPsRat(@NetworkType int rat) { mockWwanRat( NetworkRegistrationInfo.DOMAIN_PS, @@ -1088,6 +1342,7 @@ public class ServiceStateStatsTest extends TelephonyTest { doReturn(1).when(mSecondPhone).getPhoneId(); doReturn(1).when(mUiccController).getSlotIdFromPhoneId(1); doReturn(carrierId).when(mSecondPhone).getCarrierId(); + doReturn(mDataSettingsManager).when(mSecondPhone).getDataSettingsManager(); doReturn(true).when(mPhysicalSlot1).isActive(); doReturn(CardState.CARDSTATE_PRESENT).when(mPhysicalSlot1).getCardState(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java index 2ca0b167e6..b358b6daa4 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java @@ -44,13 +44,16 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import android.os.Looper; import android.telephony.AccessNetworkConstants; import android.telephony.Annotation.NetworkType; import android.telephony.DisconnectCause; import android.telephony.NetworkRegistrationInfo; +import android.telephony.PreciseDataConnectionState; import android.telephony.PreciseDisconnectCause; import android.telephony.ServiceState; import android.telephony.TelephonyManager; +import android.telephony.data.ApnSetting; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsStreamMediaProfile; import android.test.suitebuilder.annotation.SmallTest; @@ -155,19 +158,17 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { mCsCall1 = mock(GsmCdmaCall.class); mImsCall0 = mock(ImsPhoneCall.class); mImsCall1 = mock(ImsPhoneCall.class); - replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mSecondPhone}); doReturn(CARRIER_ID_SLOT_0).when(mPhone).getCarrierId(); - // mPhone's mSST/mServiceState has been set up by TelephonyTest + // mPhone's mContext/mSST/mServiceState has been set up by TelephonyTest doReturn(CARRIER_ID_SLOT_1).when(mSecondPhone).getCarrierId(); + doReturn(mContext).when(mSecondPhone).getContext(); doReturn(mSignalStrength).when(mSecondPhone).getSignalStrength(); doReturn(mSecondServiceStateTracker).when(mSecondPhone).getServiceStateTracker(); doReturn(mSecondServiceState).when(mSecondServiceStateTracker).getServiceState(); setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_UNKNOWN); - doReturn(false).when(mServiceState).getVoiceRoaming(); setServiceState(mSecondServiceState, TelephonyManager.NETWORK_TYPE_UNKNOWN); - doReturn(false).when(mSecondServiceState).getVoiceRoaming(); doReturn(true).when(mPhysicalSlot).isActive(); doReturn(CardState.CARDSTATE_PRESENT).when(mPhysicalSlot).getCardState(); @@ -196,6 +197,10 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { doReturn(PhoneConstants.PHONE_TYPE_GSM).when(mGsmConnection1).getPhoneType(); doReturn(false).when(mGsmConnection1).isEmergencyCall(); + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mVoiceCallSessionStats0 = new TestableVoiceCallSessionStats(0, mPhone); mVoiceCallSessionStats0.onServiceStateChanged(mServiceState); mVoiceCallSessionStats1 = new TestableVoiceCallSessionStats(1, mSecondPhone); @@ -204,6 +209,8 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { @After public void tearDown() throws Exception { + DataConnectionStateTracker.getInstance(0).stop(); + DataConnectionStateTracker.getInstance(1).stop(); mVoiceCallSessionStats0 = null; mVoiceCallSessionStats1 = null; super.tearDown(); @@ -754,9 +761,17 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { @SmallTest public void singleImsCall_roaming() { setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE); + NetworkRegistrationInfo roamingNri = new NetworkRegistrationInfo.Builder() + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE) + // This sets mNetworkRegistrationState + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) + .build(); + doReturn(roamingNri).when(mServiceState) + .getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_CS, + AccessNetworkConstants.TRANSPORT_TYPE_WWAN); doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech(); doReturn(mImsPhone).when(mPhone).getImsPhone(); - doReturn(true).when(mServiceState).getVoiceRoaming(); doReturn(true).when(mImsConnection0).isIncoming(); doReturn(2000L).when(mImsConnection0).getCreateTime(); doReturn(mImsCall0).when(mImsConnection0).getCall(); @@ -870,6 +885,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall.mainCodecQuality = VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND; expectedCall.ratSwitchCount = 2L; + expectedCall.ratSwitchCountAfterConnected = 2L; expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS; expectedCall.bandAtEnd = 0; expectedCall.callDuration = @@ -947,6 +963,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall.mainCodecQuality = VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND; expectedCall.ratSwitchCount = 3L; + expectedCall.ratSwitchCountAfterConnected = 3L; expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UNKNOWN; expectedCall.bandAtEnd = 0; expectedCall.callDuration = @@ -1139,6 +1156,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall0.concurrentCallCountAtStart = 0; expectedCall0.concurrentCallCountAtEnd = 1; expectedCall0.ratSwitchCount = 1L; + expectedCall0.ratSwitchCountAfterConnected = 1L; expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA; expectedCall0.bandAtEnd = 0; expectedCall0.lastKnownRat = TelephonyManager.NETWORK_TYPE_HSPA; @@ -1161,6 +1179,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall1.concurrentCallCountAtStart = 1; expectedCall1.concurrentCallCountAtEnd = 0; expectedCall1.ratSwitchCount = 2L; + expectedCall1.ratSwitchCountAfterConnected = 2L; expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS; expectedCall1.bandAtEnd = 0; expectedCall1.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS; @@ -1269,6 +1288,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall0.concurrentCallCountAtStart = 0; expectedCall0.concurrentCallCountAtEnd = 0; expectedCall0.ratSwitchCount = 2L; + expectedCall0.ratSwitchCountAfterConnected = 2L; expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS; expectedCall0.bandAtEnd = 0; expectedCall0.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS; @@ -1291,6 +1311,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall1.concurrentCallCountAtStart = 1; expectedCall1.concurrentCallCountAtEnd = 1; expectedCall1.ratSwitchCount = 1L; + expectedCall1.ratSwitchCountAfterConnected = 1L; expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA; expectedCall1.bandAtEnd = 0; expectedCall1.lastKnownRat = TelephonyManager.NETWORK_TYPE_HSPA; @@ -1399,6 +1420,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall0.concurrentCallCountAtStart = 0; expectedCall0.concurrentCallCountAtEnd = 1; expectedCall0.ratSwitchCount = 0L; + expectedCall0.ratSwitchCountAfterConnected = 0L; expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_LTE; // call 1 starts later, MT doReturn(true).when(mImsConnection1).isIncoming(); @@ -1419,6 +1441,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall1.concurrentCallCountAtStart = 1; expectedCall1.concurrentCallCountAtEnd = 0; expectedCall1.ratSwitchCount = 1L; + expectedCall1.ratSwitchCountAfterConnected = 1L; expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA; expectedCall1.bandAtEnd = 0; expectedCall1.lastKnownRat = TelephonyManager.NETWORK_TYPE_HSPA; @@ -1505,6 +1528,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall.setupDurationMillis = 5000; expectedCall.disconnectExtraCode = PreciseDisconnectCause.CALL_REJECTED; expectedCall.ratSwitchCount = 1L; + expectedCall.ratSwitchCountAfterConnected = 0L; expectedCall.setupFailed = true; expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN; expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR; @@ -1567,6 +1591,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS; expectedCall.bandAtEnd = 0; expectedCall.ratSwitchCount = 1L; + expectedCall.ratSwitchCountAfterConnected = 0L; expectedCall.setupFailed = true; expectedCall.setupDurationMillis = 13000; expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN; @@ -1626,6 +1651,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall.setupDurationMillis = 5000; expectedCall.disconnectExtraCode = PreciseDisconnectCause.NORMAL; expectedCall.ratSwitchCount = 1L; + expectedCall.ratSwitchCountAfterConnected = 0L; expectedCall.setupFailed = false; expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR; expectedCall.mainCodecQuality = @@ -1805,6 +1831,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall.setupFailed = false; expectedCall.srvccFailureCount = 2L; expectedCall.ratSwitchCount = 1L; + expectedCall.ratSwitchCountAfterConnected = 1L; expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS; expectedCall.bandAtEnd = 0; expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR; @@ -1957,6 +1984,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall.srvccCompleted = true; expectedCall.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS; expectedCall.ratSwitchCount = 1L; + expectedCall.ratSwitchCountAfterConnected = 1L; expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS; expectedCall.bandAtEnd = 0; expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR; @@ -2055,6 +2083,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall0.concurrentCallCountAtStart = 0; expectedCall0.concurrentCallCountAtEnd = 1; expectedCall0.ratSwitchCount = 1L; + expectedCall0.ratSwitchCountAfterConnected = 1L; expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS; expectedCall0.bandAtEnd = 0; expectedCall0.srvccCompleted = true; @@ -2082,6 +2111,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { expectedCall1.concurrentCallCountAtStart = 1; expectedCall1.concurrentCallCountAtEnd = 0; expectedCall1.ratSwitchCount = 1L; + expectedCall1.ratSwitchCountAfterConnected = 1L; expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS; expectedCall1.bandAtEnd = 0; expectedCall1.srvccCompleted = true; @@ -2163,6 +2193,322 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { @Test @SmallTest + public void singleCsCall_handover() { + doReturn(false).when(mGsmConnection0).isIncoming(); + doReturn(2000L).when(mGsmConnection0).getCreateTime(); + doReturn(mCsCall0).when(mGsmConnection0).getCall(); + VoiceCallSession expectedCall = + makeSlot0CallProto( + VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS, + VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO, + TelephonyManager.NETWORK_TYPE_LTE, + DisconnectCause.NORMAL); + expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UMTS; + expectedCall.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS; + expectedCall.bandAtEnd = 0; + expectedCall.setupDurationMillis = 5000; + expectedCall.disconnectExtraCode = PreciseDisconnectCause.NORMAL; + expectedCall.ratSwitchCount = 1L; + expectedCall.ratSwitchCountAfterConnected = 0L; + expectedCall.setupFailed = false; + expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR; + expectedCall.mainCodecQuality = + VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND; + expectedCall.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS; + expectedCall.handoverInProgress = false; + VoiceCallRatUsage expectedRatUsageLte = + makeRatUsageProto( + CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 3000L, 1L); + VoiceCallRatUsage expectedRatUsageUmts = + makeRatUsageProto( + CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 3000L, 12000L, 1L); + final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture(); + + mVoiceCallSessionStats0.setTimeMillis(2000L); + setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE); + doReturn(Call.State.DIALING).when(mCsCall0).getState(); + doReturn(Call.State.DIALING).when(mGsmConnection0).getState(); + doReturn(DisconnectCause.NOT_DISCONNECTED).when(mGsmConnection0).getDisconnectCause(); + mVoiceCallSessionStats0.onRilDial(mGsmConnection0); + mVoiceCallSessionStats0.setTimeMillis(3000L); + setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_UMTS); + mVoiceCallSessionStats0.onServiceStateChanged(mServiceState); + mVoiceCallSessionStats0.setTimeMillis(3100L); + mVoiceCallSessionStats0.onAudioCodecChanged(mGsmConnection0, DriverCall.AUDIO_QUALITY_AMR); + mVoiceCallSessionStats0.setTimeMillis(7000L); + doReturn(Call.State.ALERTING).when(mCsCall0).getState(); + doReturn(Call.State.ALERTING).when(mGsmConnection0).getState(); + mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0)); + mVoiceCallSessionStats0.setTimeMillis(10000L); + doReturn(Call.State.ACTIVE).when(mCsCall0).getState(); + doReturn(Call.State.ACTIVE).when(mGsmConnection0).getState(); + mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0)); + mVoiceCallSessionStats0.setTimeMillis(11000L); + // connection state changes for IMS APN shouldn't have impact on cs call + mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged( + makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + TelephonyManager.DATA_HANDOVER_IN_PROGRESS, ApnSetting.TYPE_IMS)); + mVoiceCallSessionStats0.setTimeMillis(12000L); + doReturn(DisconnectCause.NORMAL).when(mGsmConnection0).getDisconnectCause(); + doReturn(PreciseDisconnectCause.NORMAL).when(mGsmConnection0).getPreciseDisconnectCause(); + mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0)); + + ArgumentCaptor<VoiceCallSession> callCaptor = + ArgumentCaptor.forClass(VoiceCallSession.class); + verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture()); + verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any()); + verifyNoMoreInteractions(mPersistAtomsStorage); + assertProtoEquals(expectedCall, callCaptor.getValue()); + assertSortedProtoArrayEquals( + new VoiceCallRatUsage[] {expectedRatUsageLte, expectedRatUsageUmts}, + ratUsage.get()); + } + + @Test + @SmallTest + public void singleCall_callStartedDuringHandover() { + // set last IMS APN connection state with handover in progress + DataConnectionStateTracker.getInstance(0).notifyDataConnectionStateChanged( + makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + TelephonyManager.DATA_HANDOVER_IN_PROGRESS, ApnSetting.TYPE_IMS)); + setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE); + doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech(); + doReturn(true).when(mImsConnection0).isIncoming(); + doReturn(2000L).when(mImsConnection0).getCreateTime(); + doReturn(mImsCall0).when(mImsConnection0).getCall(); + doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections(); + doReturn(mImsPhone).when(mPhone).getImsPhone(); + VoiceCallSession expectedCall = + makeSlot0CallProto( + VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS, + VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT, + TelephonyManager.NETWORK_TYPE_LTE, + ImsReasonInfo.CODE_USER_TERMINATED); + expectedCall.setupDurationMillis = 80; + expectedCall.setupFailed = false; + expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR; + expectedCall.mainCodecQuality = + VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND; + expectedCall.handoverInProgress = true; + VoiceCallRatUsage expectedRatUsage = + makeRatUsageProto( + CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L); + final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture(); + + mVoiceCallSessionStats0.setTimeMillis(2000L); + doReturn(Call.State.INCOMING).when(mImsCall0).getState(); + doReturn(Call.State.INCOMING).when(mImsConnection0).getState(); + mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0); + mVoiceCallSessionStats0.setTimeMillis(2100L); + mVoiceCallSessionStats0.onAudioCodecChanged( + mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR); + mVoiceCallSessionStats0.setTimeMillis(2200L); + mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0)); + mVoiceCallSessionStats0.setTimeMillis(2280L); + doReturn(Call.State.ACTIVE).when(mImsCall0).getState(); + doReturn(Call.State.ACTIVE).when(mImsConnection0).getState(); + mVoiceCallSessionStats0.onCallStateChanged(mImsCall0); + mVoiceCallSessionStats0.setTimeMillis(12000L); + mVoiceCallSessionStats0.onImsCallTerminated( + mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0)); + + ArgumentCaptor<VoiceCallSession> callCaptor = + ArgumentCaptor.forClass(VoiceCallSession.class); + verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture()); + verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any()); + verifyNoMoreInteractions(mPersistAtomsStorage); + assertProtoEquals(expectedCall, callCaptor.getValue()); + assertThat(ratUsage.get()).hasLength(1); + assertProtoEquals(expectedRatUsage, ratUsage.get()[0]); + } + + @Test + @SmallTest + public void singleCall_callTerminatedDuringHandover() { + setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE); + doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech(); + doReturn(true).when(mImsConnection0).isIncoming(); + doReturn(2000L).when(mImsConnection0).getCreateTime(); + doReturn(mImsCall0).when(mImsConnection0).getCall(); + doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections(); + doReturn(mImsPhone).when(mPhone).getImsPhone(); + VoiceCallSession expectedCall = + makeSlot0CallProto( + VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS, + VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT, + TelephonyManager.NETWORK_TYPE_LTE, + ImsReasonInfo.CODE_USER_TERMINATED); + expectedCall.setupDurationMillis = 80; + expectedCall.setupFailed = false; + expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR; + expectedCall.mainCodecQuality = + VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND; + expectedCall.handoverInProgress = true; + VoiceCallRatUsage expectedRatUsage = + makeRatUsageProto( + CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L); + final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture(); + + mVoiceCallSessionStats0.setTimeMillis(2000L); + doReturn(Call.State.INCOMING).when(mImsCall0).getState(); + doReturn(Call.State.INCOMING).when(mImsConnection0).getState(); + mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0); + mVoiceCallSessionStats0.setTimeMillis(2100L); + mVoiceCallSessionStats0.onAudioCodecChanged( + mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR); + mVoiceCallSessionStats0.setTimeMillis(2200L); + mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0)); + mVoiceCallSessionStats0.setTimeMillis(2280L); + doReturn(Call.State.ACTIVE).when(mImsCall0).getState(); + doReturn(Call.State.ACTIVE).when(mImsConnection0).getState(); + mVoiceCallSessionStats0.onCallStateChanged(mImsCall0); + mVoiceCallSessionStats0.setTimeMillis(9500L); + mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged( + makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + TelephonyManager.DATA_HANDOVER_IN_PROGRESS, ApnSetting.TYPE_IMS)); + mVoiceCallSessionStats0.setTimeMillis(12000L); + mVoiceCallSessionStats0.onImsCallTerminated( + mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0)); + + ArgumentCaptor<VoiceCallSession> callCaptor = + ArgumentCaptor.forClass(VoiceCallSession.class); + verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture()); + verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any()); + verifyNoMoreInteractions(mPersistAtomsStorage); + assertProtoEquals(expectedCall, callCaptor.getValue()); + assertThat(ratUsage.get()).hasLength(1); + assertProtoEquals(expectedRatUsage, ratUsage.get()[0]); + } + + @Test + @SmallTest + public void singleCall_handoverSuccess() { + setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE); + doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech(); + doReturn(true).when(mImsConnection0).isIncoming(); + doReturn(2000L).when(mImsConnection0).getCreateTime(); + doReturn(mImsCall0).when(mImsConnection0).getCall(); + doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections(); + doReturn(mImsPhone).when(mPhone).getImsPhone(); + VoiceCallSession expectedCall = + makeSlot0CallProto( + VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS, + VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT, + TelephonyManager.NETWORK_TYPE_LTE, + ImsReasonInfo.CODE_USER_TERMINATED); + expectedCall.setupDurationMillis = 80; + expectedCall.setupFailed = false; + expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR; + expectedCall.mainCodecQuality = + VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND; + expectedCall.handoverInProgress = false; + VoiceCallRatUsage expectedRatUsage = + makeRatUsageProto( + CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L); + final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture(); + + mVoiceCallSessionStats0.setTimeMillis(2000L); + doReturn(Call.State.INCOMING).when(mImsCall0).getState(); + doReturn(Call.State.INCOMING).when(mImsConnection0).getState(); + mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0); + mVoiceCallSessionStats0.setTimeMillis(2100L); + mVoiceCallSessionStats0.onAudioCodecChanged( + mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR); + mVoiceCallSessionStats0.setTimeMillis(2200L); + mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0)); + mVoiceCallSessionStats0.setTimeMillis(2280L); + doReturn(Call.State.ACTIVE).when(mImsCall0).getState(); + doReturn(Call.State.ACTIVE).when(mImsConnection0).getState(); + mVoiceCallSessionStats0.onCallStateChanged(mImsCall0); + mVoiceCallSessionStats0.setTimeMillis(9500L); + mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged( + makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + TelephonyManager.DATA_HANDOVER_IN_PROGRESS, ApnSetting.TYPE_IMS)); + mVoiceCallSessionStats0.setTimeMillis(11000L); + mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged( + makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, + TelephonyManager.DATA_CONNECTED, ApnSetting.TYPE_IMS)); + mVoiceCallSessionStats0.setTimeMillis(12000L); + mVoiceCallSessionStats0.onImsCallTerminated( + mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0)); + + ArgumentCaptor<VoiceCallSession> callCaptor = + ArgumentCaptor.forClass(VoiceCallSession.class); + verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture()); + verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any()); + verifyNoMoreInteractions(mPersistAtomsStorage); + assertProtoEquals(expectedCall, callCaptor.getValue()); + assertThat(ratUsage.get()).hasLength(1); + assertProtoEquals(expectedRatUsage, ratUsage.get()[0]); + } + + @Test + @SmallTest + public void singleEmergencyCall_callTerminatedDuringHandover() { + setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE); + doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech(); + doReturn(true).when(mImsConnection0).isIncoming(); + doReturn(2000L).when(mImsConnection0).getCreateTime(); + doReturn(true).when(mImsConnection0).isEmergencyCall(); + doReturn(mImsCall0).when(mImsConnection0).getCall(); + doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections(); + doReturn(mImsPhone).when(mPhone).getImsPhone(); + VoiceCallSession expectedCall = + makeSlot0CallProto( + VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS, + VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT, + TelephonyManager.NETWORK_TYPE_LTE, + ImsReasonInfo.CODE_USER_TERMINATED); + expectedCall.setupDurationMillis = 80; + expectedCall.setupFailed = false; + expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR; + expectedCall.mainCodecQuality = + VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND; + expectedCall.isEmergency = true; + expectedCall.handoverInProgress = true; + VoiceCallRatUsage expectedRatUsage = + makeRatUsageProto( + CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L); + final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture(); + + mVoiceCallSessionStats0.setTimeMillis(2000L); + doReturn(Call.State.INCOMING).when(mImsCall0).getState(); + doReturn(Call.State.INCOMING).when(mImsConnection0).getState(); + mVoiceCallSessionStats0.onImsCallReceived(mImsConnection0); + mVoiceCallSessionStats0.setTimeMillis(2100L); + mVoiceCallSessionStats0.onAudioCodecChanged( + mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_AMR); + mVoiceCallSessionStats0.setTimeMillis(2200L); + mVoiceCallSessionStats0.onImsAcceptCall(List.of(mImsConnection0)); + mVoiceCallSessionStats0.setTimeMillis(2280L); + doReturn(Call.State.ACTIVE).when(mImsCall0).getState(); + doReturn(Call.State.ACTIVE).when(mImsConnection0).getState(); + mVoiceCallSessionStats0.onCallStateChanged(mImsCall0); + mVoiceCallSessionStats0.setTimeMillis(9500L); + mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged( + makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + TelephonyManager.DATA_HANDOVER_IN_PROGRESS, ApnSetting.TYPE_EMERGENCY)); + mVoiceCallSessionStats0.setTimeMillis(10000L); + // connection state changes for IMS APN shouldn't have impact on emergency call + mVoiceCallSessionStats0.onPreciseDataConnectionStateChanged( + makePreciseDataConnectionState(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, + TelephonyManager.DATA_CONNECTED, ApnSetting.TYPE_IMS)); + mVoiceCallSessionStats0.setTimeMillis(12000L); + mVoiceCallSessionStats0.onImsCallTerminated( + mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0)); + + ArgumentCaptor<VoiceCallSession> callCaptor = + ArgumentCaptor.forClass(VoiceCallSession.class); + verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture()); + verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any()); + verifyNoMoreInteractions(mPersistAtomsStorage); + assertProtoEquals(expectedCall, callCaptor.getValue()); + assertThat(ratUsage.get()).hasLength(1); + assertProtoEquals(expectedRatUsage, ratUsage.get()[0]); + } + + @Test + @SmallTest public void singleWifiCall_preferred() { setServiceStateWithWifiCalling(mServiceState, TelephonyManager.NETWORK_TYPE_LTE); doReturn(mImsPhone).when(mPhone).getImsPhone(); @@ -2329,6 +2675,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { call.lastKnownRat = rat; call.bandAtEnd = 1; call.ratSwitchCount = 0L; + call.ratSwitchCountAfterConnected = 0L; call.codecBitmask = 0L; call.mainCodecQuality = VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN; call.simSlotIndex = 0; @@ -2363,6 +2710,7 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { call.lastKnownRat = rat; call.bandAtEnd = 1; call.ratSwitchCount = 0L; + call.ratSwitchCountAfterConnected = 0L; call.codecBitmask = 0L; call.mainCodecQuality = VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN; call.simSlotIndex = 1; @@ -2390,6 +2738,22 @@ public class VoiceCallSessionStatsTest extends TelephonyTest { return usage; } + private static PreciseDataConnectionState makePreciseDataConnectionState(int transport, + int state, int apnType) { + return new PreciseDataConnectionState.Builder() + .setTransportType(transport) + .setId(1) + .setState(state) + .setApnSetting(new ApnSetting.Builder() + .setApnTypeBitmask(apnType) + .setApnName("ims,emergency") + .setEntryName("ims,emergency") + .build()) + .setLinkProperties(new android.net.LinkProperties()) + .setFailCause(0) + .build(); + } + private static void assertProtoEquals(MessageNano expected, MessageNano actual) { assertWithMessage( " actual proto:\n" diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java new file mode 100644 index 0000000000..c202f0cf00 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java @@ -0,0 +1,137 @@ +/* + * 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.satellite; + +import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN; +import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; +import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING; +import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_DATA; +import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY; +import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_SMS; +import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; + +import android.annotation.NonNull; +import android.telephony.CellIdentity; +import android.telephony.CellIdentityGsm; +import android.telephony.NetworkRegistrationInfo; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.ArraySet; + +import com.android.internal.telephony.TelephonyTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NtnCapabilityResolverTest extends TelephonyTest { + private static final String TAG = "NtnCapabilityResolverTest"; + private static final int SUB_ID = 0; + private static final String VISITING_PLMN = "00102"; + private static final String SATELLITE_PLMN = "00103"; + private static final String[] SATELLITE_PLMN_ARRAY = {SATELLITE_PLMN}; + + private final int[] mSatelliteSupportedServices = {SERVICE_TYPE_SMS, SERVICE_TYPE_EMERGENCY}; + private final List<Integer> mSatelliteSupportedServiceList = + Arrays.stream(mSatelliteSupportedServices).boxed().collect(Collectors.toList()); + @Mock private SatelliteController mMockSatelliteController; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + logd(TAG + " Setup!"); + + replaceInstance(SatelliteController.class, "sInstance", null, + mMockSatelliteController); + doReturn(Arrays.asList(SATELLITE_PLMN_ARRAY)) + .when(mMockSatelliteController).getSatellitePlmnList(); + doReturn(mSatelliteSupportedServiceList).when(mMockSatelliteController) + .getSupportedSatelliteServices(SUB_ID, SATELLITE_PLMN); + } + + @After + public void tearDown() throws Exception { + logd(TAG + " tearDown"); + super.tearDown(); + } + + @Test + public void testResolveNTNCapability() { + // Test resolving a satellite NetworkRegistrationInfo. + NetworkRegistrationInfo satelliteNri = createNetworkRegistrationInfo(SATELLITE_PLMN); + NetworkRegistrationInfo originalNri = new NetworkRegistrationInfo(satelliteNri); + + assertEquals(satelliteNri, originalNri); + assertFalse(satelliteNri.isNonTerrestrialNetwork()); + assertFalse(Arrays.equals(mSatelliteSupportedServices, + satelliteNri.getAvailableServices().stream() + .mapToInt(Integer::intValue) + .toArray())); + NtnCapabilityResolver.resolveNtnCapability(satelliteNri, SUB_ID); + assertNotEquals(satelliteNri, originalNri); + assertTrue(satelliteNri.isNonTerrestrialNetwork()); + assertTrue(Arrays.equals(mSatelliteSupportedServices, + satelliteNri.getAvailableServices().stream() + .mapToInt(Integer::intValue) + .toArray())); + + // Test resolving a non-satellite NetworkRegistrationInfo. + NetworkRegistrationInfo cellularNri = createNetworkRegistrationInfo(VISITING_PLMN); + originalNri = new NetworkRegistrationInfo(cellularNri); + + assertEquals(cellularNri, originalNri); + assertFalse(cellularNri.isNonTerrestrialNetwork()); + assertFalse(Arrays.equals(mSatelliteSupportedServices, + cellularNri.getAvailableServices().stream() + .mapToInt(Integer::intValue) + .toArray())); + NtnCapabilityResolver.resolveNtnCapability(cellularNri, SUB_ID); + assertEquals(cellularNri, originalNri); + assertFalse(cellularNri.isNonTerrestrialNetwork()); + assertFalse(Arrays.equals(mSatelliteSupportedServices, + cellularNri.getAvailableServices().stream() + .mapToInt(Integer::intValue) + .toArray())); + } + + private NetworkRegistrationInfo createNetworkRegistrationInfo(@NonNull String registeredPlmn) { + List<Integer> availableServices = new ArrayList<>(); + availableServices.add(SERVICE_TYPE_DATA); + CellIdentity cellIdentity = new CellIdentityGsm(0, 0, 0, + 0, "mcc", "mnc", "", "", new ArraySet<>()); + return new NetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN, + REGISTRATION_STATE_ROAMING, NETWORK_TYPE_LTE, 0, false, availableServices, + cellIdentity, registeredPlmn, false, 0, 0, 0); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java new file mode 100644 index 0000000000..4587b7cfa9 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java @@ -0,0 +1,577 @@ +/* + * 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.satellite; + +import android.os.Bundle; +import android.os.Handler; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.Intent; +import android.os.AsyncResult; +import android.os.Message; +import android.telephony.satellite.ISatelliteTransmissionUpdateCallback; +import android.telephony.satellite.PointingInfo; +import android.telephony.satellite.SatelliteManager; +import android.telephony.satellite.SatelliteManager.SatelliteException; +import android.util.Log; + +import com.android.internal.R; +import com.android.internal.telephony.TelephonyTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class PointingAppControllerTest extends TelephonyTest { + private static final String TAG = "PointingAppControllerTest"; + private static final int SUB_ID = 0; + private static final long TIMEOUT = 500; + + //Events For SatelliteControllerHandler + private static final int EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE = 2; + private static final int EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE = 4; + private static final String KEY_POINTING_UI_PACKAGE_NAME = "default_pointing_ui_package"; + private static final String KEY_POINTING_UI_CLASS_NAME = "default_pointing_ui_class"; + private static final String KEY_NEED_FULL_SCREEN = "needFullScreen"; + + private PointingAppController mPointingAppController; + InOrder mInOrder; + + @Mock private SatelliteModemInterface mMockSatelliteModemInterface; + @Mock private SatelliteController mMockSatelliteController; + + private TestSatelliteTransmissionUpdateCallback mSatelliteTransmissionUpdateCallback; + private TestSatelliteControllerHandler mTestSatelliteControllerHandler; + /** Variables required to receive datagrams in the unit tests. */ + LinkedBlockingQueue<Integer> mResultListener; + int mResultCode = -1; + private Semaphore mSendDatagramStateSemaphore = new Semaphore(0); + private Semaphore mReceiveDatagramStateSemaphore = new Semaphore(0); + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + logd(TAG + " Setup!"); + + replaceInstance(SatelliteModemInterface.class, "sInstance", null, + mMockSatelliteModemInterface); + replaceInstance(SatelliteController.class, "sInstance", null, + mMockSatelliteController); + mPointingAppController = new PointingAppController(mContext); + mContextFixture.putResource(R.string.config_pointing_ui_package, + KEY_POINTING_UI_PACKAGE_NAME); + mContextFixture.putResource(R.string.config_pointing_ui_class, + KEY_POINTING_UI_CLASS_NAME); + mResultListener = new LinkedBlockingQueue<>(1); + mSatelliteTransmissionUpdateCallback = new TestSatelliteTransmissionUpdateCallback(); + mTestSatelliteControllerHandler = new TestSatelliteControllerHandler(); + } + + @After + public void tearDown() throws Exception { + logd(TAG + " tearDown"); + mResultListener = null; + + mSatelliteTransmissionUpdateCallback = null; + super.tearDown(); + } + + private class TestSatelliteControllerHandler extends Handler { + @Override + public void handleMessage(Message msg) { + AsyncResult ar; + + switch(msg.what) { + case EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE: { + ar = (AsyncResult) msg.obj; + mResultCode = SatelliteServiceUtils.getSatelliteError(ar, + "startSatelliteTransmissionUpdates"); + logd("mResultCode = " + mResultCode); + break; + } + case EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE: { + ar = (AsyncResult) msg.obj; + mResultCode = SatelliteServiceUtils.getSatelliteError(ar, + "stopSatelliteTransmissionUpdates"); + logd("mResultCode = " + mResultCode); + break; + } + default: + logd("TestSatelliteController: " + msg.what); + break; + } + } + } + private class TestSatelliteTransmissionUpdateCallback + extends ISatelliteTransmissionUpdateCallback.Stub { + int mState; + int mSendPendingCount; + int mReceivePendingCount; + int mErrorCode; + public boolean inSendDatagramStateCallback = false; + public boolean inReceiveDatagramStateCallback = false; + + @Override + public void onSatellitePositionChanged(PointingInfo pointingInfo) { + //Not Used + } + + @Override + public void onSendDatagramStateChanged(int state, int sendPendingCount, + int errorCode) { + mState = state; + mSendPendingCount = sendPendingCount; + mErrorCode = errorCode; + inSendDatagramStateCallback = true; + try { + mSendDatagramStateSemaphore.release(); + } catch (Exception ex) { + loge("mSendDatagramStateSemaphore: Got exception in releasing semaphore, ex=" + ex); + } + } + + @Override + public void onReceiveDatagramStateChanged(int state, + int receivePendingCount, int errorCode) { + mState = state; + mReceivePendingCount = receivePendingCount; + mErrorCode = errorCode; + inReceiveDatagramStateCallback = true; + try { + mReceiveDatagramStateSemaphore.release(); + } catch (Exception ex) { + loge("mReceiveDatagramStateSemaphore: Got exception in releasing semaphore, ex=" + + ex); + } + } + + public int getState() { + return mState; + } + + public int getSendPendingCount() { + return mSendPendingCount; + } + + public int getReceivePendingCount() { + return mReceivePendingCount; + } + + public int getErrorCode() { + return mErrorCode; + } + } + + private boolean waitForReceiveDatagramStateChangedRessult( + int expectedNumberOfEvents) { + for (int i = 0; i < expectedNumberOfEvents; i++) { + try { + if (!mReceiveDatagramStateSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) { + loge("Timeout to receive " + + "ReceiveDatagramStateChanged event"); + return false; + } + } catch (Exception ex) { + loge("waitForReceiveDatagramStateChangedRessult: Got exception=" + ex); + return false; + } + } + return true; + } + + private boolean waitForSendDatagramStateChangedRessult( + int expectedNumberOfEvents) { + for (int i = 0; i < expectedNumberOfEvents; i++) { + try { + if (!mSendDatagramStateSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) { + loge("Timeout to receive " + + "SendDatagramStateChanged event"); + return false; + } + } catch (Exception ex) { + loge("waitForSendDatagramStateChangedRessult: Got exception=" + ex); + return false; + } + } + return true; + } + + private void setUpResponseForStartTransmissionUpdates( + @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == SatelliteManager.SATELLITE_ERROR_NONE) + ? null : new SatelliteException(error); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + AsyncResult.forMessage(message, null, exception); + message.sendToTarget(); + return null; + }).when(mPhone).startSatellitePositionUpdates(any(Message.class)); + + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + AsyncResult.forMessage(message, null, exception); + message.sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface).startSendingSatellitePointingInfo(any(Message.class)); + } + + private void setUpResponseForStopTransmissionUpdates( + @SatelliteManager.SatelliteError int error) { + SatelliteException exception = (error == SatelliteManager.SATELLITE_ERROR_NONE) + ? null : new SatelliteException(error); + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + AsyncResult.forMessage(message, null, exception); + message.sendToTarget(); + return null; + }).when(mPhone).stopSatellitePositionUpdates(any(Message.class)); + + doAnswer(invocation -> { + Message message = (Message) invocation.getArguments()[0]; + AsyncResult.forMessage(message, null, exception); + message.sendToTarget(); + return null; + }).when(mMockSatelliteModemInterface).stopSendingSatellitePointingInfo(any(Message.class)); + } + + @Test + public void testStartSatelliteTransmissionUpdates_CommandInterface() + throws Exception { + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + Message testMessage = mTestSatelliteControllerHandler + .obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, null); + setUpResponseForStartTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE); + mPointingAppController.startSatelliteTransmissionUpdates(testMessage, mPhone); + + processAllMessages(); + + verify(mMockSatelliteModemInterface, never()) + .startSendingSatellitePointingInfo(eq(testMessage)); + + verify(mPhone) + .startSatellitePositionUpdates(eq(testMessage)); + + assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode); + + assertTrue(mPointingAppController.getStartedSatelliteTransmissionUpdates()); + } + + @Test + public void testStartSatelliteTransmissionUpdates_success() + throws Exception { + doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + mPointingAppController.setStartedSatelliteTransmissionUpdates(false); + Message testMessage = mTestSatelliteControllerHandler + .obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, null); + setUpResponseForStartTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE); + mPointingAppController.startSatelliteTransmissionUpdates(testMessage, mPhone); + + verify(mMockSatelliteModemInterface) + .startSendingSatellitePointingInfo(eq(testMessage)); + + verify(mPhone, never()) + .startSatellitePositionUpdates(eq(testMessage)); + + processAllMessages(); + + + assertTrue(mPointingAppController.getStartedSatelliteTransmissionUpdates()); + assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode); + } + + @Test + public void testStartSatelliteTransmissionUpdates_phoneNull() + throws Exception { + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + mPointingAppController.setStartedSatelliteTransmissionUpdates(false); + Message testMessage = mTestSatelliteControllerHandler + .obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, null); + + mPointingAppController.startSatelliteTransmissionUpdates(testMessage, null); + processAllMessages(); + verify(mMockSatelliteModemInterface, never()) + .startSendingSatellitePointingInfo(eq(testMessage)); + + verify(mPhone, never()) + .startSatellitePositionUpdates(eq(testMessage)); + + assertFalse(mPointingAppController.getStartedSatelliteTransmissionUpdates()); + + assertEquals(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, mResultCode); + } + + @Test + public void testStopSatelliteTransmissionUpdates_CommandInterface() + throws Exception { + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + setUpResponseForStopTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE); + Message testMessage = mTestSatelliteControllerHandler + .obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, null); + mPointingAppController.stopSatelliteTransmissionUpdates(testMessage, mPhone); + + processAllMessages(); + + verify(mMockSatelliteModemInterface, never()) + .stopSendingSatellitePointingInfo(eq(testMessage)); + + verify(mPhone) + .stopSatellitePositionUpdates(eq(testMessage)); + + assertFalse(mPointingAppController.getStartedSatelliteTransmissionUpdates()); + + assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode); + } + + @Test + public void testStopSatelliteTransmissionUpdates_success() + throws Exception { + doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + setUpResponseForStopTransmissionUpdates(SatelliteManager.SATELLITE_ERROR_NONE); + Message testMessage = mTestSatelliteControllerHandler + .obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, null); + mPointingAppController.stopSatelliteTransmissionUpdates(testMessage, mPhone); + + processAllMessages(); + + verify(mMockSatelliteModemInterface) + .stopSendingSatellitePointingInfo(eq(testMessage)); + + verify(mPhone, never()) + .stopSatellitePositionUpdates(eq(testMessage)); + + assertFalse(mPointingAppController.getStartedSatelliteTransmissionUpdates()); + assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, mResultCode); + } + + @Test + public void testStopSatellitePointingInfo_phoneNull() + throws Exception { + doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + Message testMessage = mTestSatelliteControllerHandler + .obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, null); + mPointingAppController.stopSatelliteTransmissionUpdates(testMessage, null); + + processAllMessages(); + + verify(mMockSatelliteModemInterface, never()) + .stopSendingSatellitePointingInfo(eq(testMessage)); + + verify(mPhone, never()) + .stopSatellitePositionUpdates(eq(testMessage)); + + assertEquals(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, mResultCode); + + } + + @Test + public void testStartPointingUI() throws Exception { + ArgumentCaptor<Intent> startedIntentCaptor = ArgumentCaptor.forClass(Intent.class); + mPointingAppController.startPointingUI(true); + verify(mContext).startActivity(startedIntentCaptor.capture()); + Intent intent = startedIntentCaptor.getValue(); + assertEquals(KEY_POINTING_UI_PACKAGE_NAME, intent.getComponent().getPackageName()); + assertEquals(KEY_POINTING_UI_CLASS_NAME, intent.getComponent().getClassName()); + Bundle b = intent.getExtras(); + assertTrue(b.containsKey(KEY_NEED_FULL_SCREEN)); + assertTrue(b.getBoolean(KEY_NEED_FULL_SCREEN)); + } + + @Test + public void testUpdateSendDatagramTransferState() throws Exception { + mPointingAppController.registerForSatelliteTransmissionUpdates(SUB_ID, + mSatelliteTransmissionUpdateCallback, mPhone); + mPointingAppController.updateSendDatagramTransferState(SUB_ID, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS, 1, + SatelliteManager.SATELLITE_ERROR_NONE); + assertTrue(waitForSendDatagramStateChangedRessult(1)); + assertEquals(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS, + mSatelliteTransmissionUpdateCallback.getState()); + assertEquals(1, mSatelliteTransmissionUpdateCallback.getSendPendingCount()); + assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, + mSatelliteTransmissionUpdateCallback.getErrorCode()); + assertTrue(mSatelliteTransmissionUpdateCallback.inSendDatagramStateCallback); + mPointingAppController.unregisterForSatelliteTransmissionUpdates(SUB_ID, + mResultListener::offer, mSatelliteTransmissionUpdateCallback, mPhone); + mResultListener.clear(); + } + + @Test + public void testUpdateReceiveDatagramTransferState() throws Exception { + mPointingAppController.registerForSatelliteTransmissionUpdates(SUB_ID, + mSatelliteTransmissionUpdateCallback, mPhone); + mPointingAppController.updateReceiveDatagramTransferState(SUB_ID, + SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS, 2, + SatelliteManager.SATELLITE_ERROR_NONE); + assertTrue(waitForReceiveDatagramStateChangedRessult(1)); + assertEquals(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS, + mSatelliteTransmissionUpdateCallback.getState()); + assertEquals(2, mSatelliteTransmissionUpdateCallback.getReceivePendingCount()); + assertEquals(SatelliteManager.SATELLITE_ERROR_NONE, + mSatelliteTransmissionUpdateCallback.getErrorCode()); + assertTrue(mSatelliteTransmissionUpdateCallback.inReceiveDatagramStateCallback); + mPointingAppController.unregisterForSatelliteTransmissionUpdates(SUB_ID, + mResultListener::offer, mSatelliteTransmissionUpdateCallback, mPhone); + mResultListener.clear(); + } + + @Test + public void testRegisterForSatelliteTransmissionUpdates_CommandInterface() throws Exception { + mResultListener.clear(); + mInOrder = inOrder(mPhone); + TestSatelliteTransmissionUpdateCallback callback1 = new + TestSatelliteTransmissionUpdateCallback(); + TestSatelliteTransmissionUpdateCallback callback2 = new + TestSatelliteTransmissionUpdateCallback(); + int subId1 = 1; + int subId2 = 2; + mPointingAppController.registerForSatelliteTransmissionUpdates(subId1, + callback1, mPhone); + mInOrder.verify(mPhone).registerForSatellitePositionInfoChanged(any(), + eq(1), eq(null)); + mPointingAppController.registerForSatelliteTransmissionUpdates(subId1, + callback2, mPhone); + mInOrder.verify(mPhone, never()).registerForSatellitePositionInfoChanged(any(), + eq(1), eq(null)); + mPointingAppController.registerForSatelliteTransmissionUpdates(subId2, + callback1, mPhone); + mInOrder.verify(mPhone).registerForSatellitePositionInfoChanged(any(), + eq(1), eq(null)); + mPointingAppController.registerForSatelliteTransmissionUpdates(subId2, + callback2, mPhone); + mInOrder.verify(mPhone, never()).registerForSatellitePositionInfoChanged(any(), + eq(1), eq(null)); + mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1, + mResultListener::offer, callback1, mPhone); + processAllMessages(); + //since there are 2 callbacks registered for this sub_id, Handler is not unregistered + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE); + mResultListener.remove(); + mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1, + mResultListener::offer, callback2, mPhone); + mInOrder.verify(mPhone).unregisterForSatellitePositionInfoChanged(any(Handler.class)); + mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2, + mResultListener::offer, callback1, mPhone); + processAllMessages(); + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE); + mResultListener.remove(); + mInOrder.verify(mPhone, never()).unregisterForSatellitePositionInfoChanged( + any(Handler.class)); + mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2, + mResultListener::offer, callback2, null); + processAllMessages(); + assertThat(mResultListener.peek()) + .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE); + mResultListener.remove(); + mInOrder = null; + } + + @Test + public void testRegisterForSatelliteTransmissionUpdates() throws Exception { + mResultListener.clear(); + doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported(); + mInOrder = inOrder(mMockSatelliteModemInterface); + TestSatelliteTransmissionUpdateCallback callback1 = new + TestSatelliteTransmissionUpdateCallback(); + TestSatelliteTransmissionUpdateCallback callback2 = new + TestSatelliteTransmissionUpdateCallback(); + int subId1 = 3; + int subId2 = 4; + mPointingAppController.registerForSatelliteTransmissionUpdates(subId1, + callback1, mPhone); + mInOrder.verify(mMockSatelliteModemInterface).registerForSatellitePositionInfoChanged(any(), + eq(1), eq(null)); + mInOrder.verify(mMockSatelliteModemInterface).registerForDatagramTransferStateChanged(any(), + eq(4), eq(null)); + mPointingAppController.registerForSatelliteTransmissionUpdates(subId1, + callback2, mPhone); + mInOrder.verify(mMockSatelliteModemInterface, never()) + .registerForSatellitePositionInfoChanged(any(), eq(1), eq(null)); + mInOrder.verify(mMockSatelliteModemInterface, never()) + .registerForDatagramTransferStateChanged(any(), eq(4), eq(null)); + mPointingAppController.registerForSatelliteTransmissionUpdates(subId2, + callback1, mPhone); + mInOrder.verify(mMockSatelliteModemInterface).registerForSatellitePositionInfoChanged(any(), + eq(1), eq(null)); + mInOrder.verify(mMockSatelliteModemInterface).registerForDatagramTransferStateChanged(any(), + eq(4), eq(null)); + mPointingAppController.registerForSatelliteTransmissionUpdates(subId2, + callback2, mPhone); + mInOrder.verify(mMockSatelliteModemInterface, never()) + .registerForSatellitePositionInfoChanged(any(), eq(1), eq(null)); + mInOrder.verify(mMockSatelliteModemInterface, never()) + .registerForDatagramTransferStateChanged(any(), eq(4), eq(null)); + mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1, + mResultListener::offer, callback1, mPhone); + processAllMessages(); + //since there are 2 callbacks registered for this sub_id, Handler is not unregistered + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE); + mResultListener.remove(); + mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1, + mResultListener::offer, callback2, mPhone); + mInOrder.verify(mMockSatelliteModemInterface).unregisterForSatellitePositionInfoChanged( + any(Handler.class)); + mInOrder.verify(mMockSatelliteModemInterface).unregisterForDatagramTransferStateChanged( + any(Handler.class)); + mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2, + mResultListener::offer, callback1, mPhone); + processAllMessages(); + assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE); + mResultListener.remove(); + mInOrder.verify(mMockSatelliteModemInterface, never()) + .unregisterForSatellitePositionInfoChanged(any(Handler.class)); + mInOrder.verify(mMockSatelliteModemInterface, never()) + .unregisterForDatagramTransferStateChanged(any(Handler.class)); + mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2, + mResultListener::offer, callback2, null); + processAllMessages(); + mInOrder.verify(mMockSatelliteModemInterface).unregisterForSatellitePositionInfoChanged( + any(Handler.class)); + mInOrder.verify(mMockSatelliteModemInterface).unregisterForDatagramTransferStateChanged( + any(Handler.class)); + mInOrder = null; + } + + private static void loge(String message) { + Log.e(TAG, message); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java index f6ed2e24df..edfd6105ad 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java @@ -53,7 +53,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.anyVararg; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -72,7 +74,9 @@ import android.os.Handler; import android.os.ICancellationSignal; import android.os.Looper; import android.os.Message; +import android.os.PersistableBundle; import android.os.ResultReceiver; +import android.telephony.CarrierConfigManager; import android.telephony.Rlog; import android.telephony.satellite.ISatelliteDatagramCallback; import android.telephony.satellite.ISatelliteProvisionStateCallback; @@ -84,7 +88,9 @@ import android.telephony.satellite.SatelliteManager; import android.telephony.satellite.SatelliteManager.SatelliteException; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Pair; +import com.android.internal.R; import com.android.internal.telephony.IIntegerConsumer; import com.android.internal.telephony.IVoidConsumer; import com.android.internal.telephony.Phone; @@ -92,6 +98,7 @@ import com.android.internal.telephony.TelephonyTest; import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats; import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats; import com.android.internal.telephony.satellite.metrics.SessionMetricsStats; +import com.android.internal.telephony.subscription.SubscriptionManagerService; import org.junit.After; import org.junit.Before; @@ -107,6 +114,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Executor; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -114,11 +122,17 @@ import java.util.concurrent.TimeUnit; @TestableLooper.RunWithLooper public class SatelliteControllerTest extends TelephonyTest { private static final String TAG = "SatelliteControllerTest"; + + private static final int EVENT_DEVICE_CONFIG_CHANGED = 29; + private static final long TIMEOUT = 500; private static final int SUB_ID = 0; + private static final int SUB_ID1 = 1; private static final int MAX_BYTES_PER_OUT_GOING_DATAGRAM = 339; private static final String TEST_SATELLITE_TOKEN = "TEST_SATELLITE_TOKEN"; private static final String TEST_NEXT_SATELLITE_TOKEN = "TEST_NEXT_SATELLITE_TOKEN"; + private static final String[] EMPTY_SATELLITE_SERVICES_SUPPORTED_BY_PROVIDERS_STRING_ARRAY = {}; + private static final int[] ACTIVE_SUB_IDS = {SUB_ID}; private TestSatelliteController mSatelliteControllerUT; private TestSharedPreferences mSharedPreferences; @@ -130,6 +144,7 @@ public class SatelliteControllerTest extends TelephonyTest { @Mock private ControllerMetricsStats mMockControllerMetricsStats; @Mock private ProvisionMetricsStats mMockProvisionMetricsStats; @Mock private SessionMetricsStats mMockSessionMetricsStats; + @Mock private SubscriptionManagerService mMockSubscriptionManagerService; private List<Integer> mIIntegerConsumerResults = new ArrayList<>(); @Mock private ISatelliteTransmissionUpdateCallback mStartTransmissionUpdateCallback; @Mock private ISatelliteTransmissionUpdateCallback mStopTransmissionUpdateCallback; @@ -215,6 +230,7 @@ public class SatelliteControllerTest extends TelephonyTest { private ResultReceiver mIsSatelliteEnabledReceiver = new ResultReceiver(null) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { + logd("mIsSatelliteEnabledReceiver: resultCode=" + resultCode); mQueriedIsSatelliteEnabledResultCode = resultCode; if (resultCode == SATELLITE_ERROR_NONE) { if (resultData.containsKey(KEY_SATELLITE_ENABLED)) { @@ -224,7 +240,6 @@ public class SatelliteControllerTest extends TelephonyTest { mQueriedIsSatelliteEnabled = false; } } else { - logd("mIsSatelliteEnableReceiver: resultCode=" + resultCode); mQueriedIsSatelliteEnabled = false; } try { @@ -343,6 +358,9 @@ public class SatelliteControllerTest extends TelephonyTest { } }; + private List<Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener>> + mCarrierConfigChangedListenerList = new ArrayList<>(); + private PersistableBundle mCarrierConfigBundle; @Before public void setUp() throws Exception { @@ -364,6 +382,25 @@ public class SatelliteControllerTest extends TelephonyTest { mMockProvisionMetricsStats); replaceInstance(SessionMetricsStats.class, "sInstance", null, mMockSessionMetricsStats); + replaceInstance(SubscriptionManagerService.class, "sInstance", null, + mMockSubscriptionManagerService); + + mContextFixture.putStringArrayResource( + R.array.config_satellite_services_supported_by_providers, + EMPTY_SATELLITE_SERVICES_SUPPORTED_BY_PROVIDERS_STRING_ARRAY); + doReturn(ACTIVE_SUB_IDS).when(mMockSubscriptionManagerService).getActiveSubIdList(true); + + mCarrierConfigBundle = mContextFixture.getCarrierConfigBundle(); + doReturn(mCarrierConfigBundle) + .when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyVararg()); + doAnswer(invocation -> { + Executor executor = invocation.getArgument(0); + CarrierConfigManager.CarrierConfigChangeListener listener = invocation.getArgument(1); + mCarrierConfigChangedListenerList.add(new Pair<>(executor, listener)); + return null; + }).when(mCarrierConfigManager).registerCarrierConfigChangeListener( + any(Executor.class), + any(CarrierConfigManager.CarrierConfigChangeListener.class)); mSharedPreferences = new TestSharedPreferences(); when(mContext.getSharedPreferences(anyString(), anyInt())).thenReturn(mSharedPreferences); @@ -586,18 +623,39 @@ public class SatelliteControllerTest extends TelephonyTest { processAllMessages(); verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE); + // Successfully enable satellite + mIIntegerConsumerResults.clear(); + mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false; + setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE); + mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); + processAllMessages(); + assertTrue(waitForIIntegerConsumerResult(1)); + assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0)); + verifySatelliteEnabled(true, SATELLITE_ERROR_NONE); + assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled); + assertEquals( + SATELLITE_MODE_ENABLED_TRUE, mSatelliteControllerUT.satelliteModeSettingValue); + verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(true)); + verify(mMockSatelliteSessionController, times(2)).setDemoMode(eq(false)); + verify(mMockDatagramController, times(2)).setDemoMode(eq(false)); + verify(mMockPointingAppController).startPointingUI(eq(false)); + verify(mMockControllerMetricsStats, times(1)).onSatelliteEnabled(); + verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementSuccessCount(); + // Successfully disable satellite when radio is turned off. mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false; setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE); setRadioPower(false); processAllMessages(); + sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null); + processAllMessages(); verifySatelliteEnabled(false, SATELLITE_ERROR_NONE); assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled); assertEquals( SATELLITE_MODE_ENABLED_FALSE, mSatelliteControllerUT.satelliteModeSettingValue); verify(mMockSatelliteSessionController, times(2)).onSatelliteEnabledStateChanged(eq(false)); - verify(mMockSatelliteSessionController, times(2)).setDemoMode(eq(false)); - verify(mMockDatagramController, times(2)).setDemoMode(eq(false)); + verify(mMockSatelliteSessionController, times(3)).setDemoMode(eq(false)); + verify(mMockDatagramController, times(3)).setDemoMode(eq(false)); verify(mMockControllerMetricsStats, times(1)).onSatelliteDisabled(); // Fail to enable satellite when radio is off. @@ -615,6 +673,7 @@ public class SatelliteControllerTest extends TelephonyTest { // Fail to enable satellite with an error response from modem when radio is on. mIIntegerConsumerResults.clear(); + clearInvocations(mMockPointingAppController); mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false; setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_INVALID_MODEM_STATE); mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer); @@ -638,14 +697,14 @@ public class SatelliteControllerTest extends TelephonyTest { assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled); assertEquals(SATELLITE_MODE_ENABLED_TRUE, mSatelliteControllerUT.satelliteModeSettingValue); verify(mMockPointingAppController).startPointingUI(eq(false)); - verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(true)); - verify(mMockSatelliteSessionController, times(3)).setDemoMode(eq(false)); - verify(mMockDatagramController, times(3)).setDemoMode(eq(false)); - verify(mMockControllerMetricsStats, times(1)).onSatelliteEnabled(); - verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementSuccessCount(); - verify(mMockSessionMetricsStats, times(2)).setInitializationResult(anyInt()); - verify(mMockSessionMetricsStats, times(2)).setRadioTechnology(anyInt()); - verify(mMockSessionMetricsStats, times(2)).reportSessionMetrics(); + verify(mMockSatelliteSessionController, times(2)).onSatelliteEnabledStateChanged(eq(true)); + verify(mMockSatelliteSessionController, times(4)).setDemoMode(eq(false)); + verify(mMockDatagramController, times(4)).setDemoMode(eq(false)); + verify(mMockControllerMetricsStats, times(2)).onSatelliteEnabled(); + verify(mMockControllerMetricsStats, times(2)).reportServiceEnablementSuccessCount(); + verify(mMockSessionMetricsStats, times(3)).setInitializationResult(anyInt()); + verify(mMockSessionMetricsStats, times(3)).setRadioTechnology(anyInt()); + verify(mMockSessionMetricsStats, times(3)).reportSessionMetrics(); // Successfully enable satellite when it is already enabled. mIIntegerConsumerResults.clear(); @@ -663,7 +722,7 @@ public class SatelliteControllerTest extends TelephonyTest { assertEquals(SATELLITE_INVALID_ARGUMENTS, (long) mIIntegerConsumerResults.get(0)); verifySatelliteEnabled(true, SATELLITE_ERROR_NONE); - // Disable satellite. + // Successfully disable satellite. mIIntegerConsumerResults.clear(); setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE); mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer); @@ -1412,13 +1471,128 @@ public class SatelliteControllerTest extends TelephonyTest { processAllMessages(); assertTrue(waitForIIntegerConsumerResult(1)); assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0)); + } + @Test + public void testSupportedSatelliteServices() { + List<String> satellitePlmnList = mSatelliteControllerUT.getSatellitePlmnList(); + assertEquals(EMPTY_SATELLITE_SERVICES_SUPPORTED_BY_PROVIDERS_STRING_ARRAY.length, + satellitePlmnList.size()); + List<Integer> supportedSatelliteServices = + mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00101"); + assertTrue(supportedSatelliteServices.isEmpty()); + + String[] satellitePlmnArray = {"00101", "00102"}; + String[] satelliteServicesSupportedByProviderStrArray = {"00101:1,2", "00102:2,3"}; + int[] expectedSupportedServices1 = {1, 2}; + int[] expectedSupportedServices2 = {2, 3}; + + mContextFixture.putStringArrayResource( + R.array.config_satellite_services_supported_by_providers, + satelliteServicesSupportedByProviderStrArray); + TestSatelliteController testSatelliteController = + new TestSatelliteController(mContext, Looper.myLooper()); + + satellitePlmnList = testSatelliteController.getSatellitePlmnList(); + assertTrue(Arrays.equals(satellitePlmnArray, satellitePlmnList.stream().toArray())); + + supportedSatelliteServices = + testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101"); + assertNotNull(supportedSatelliteServices); + assertTrue(Arrays.equals(expectedSupportedServices1, + supportedSatelliteServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + supportedSatelliteServices = + testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102"); + assertNotNull(supportedSatelliteServices); + assertTrue(Arrays.equals(expectedSupportedServices2, + supportedSatelliteServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + // Carrier config changed + int[] expectedSupportedServices3 = {2}; + int[] supportedServices = {1, 3}; + PersistableBundle carrierSupportedSatelliteServicesPerProvider = new PersistableBundle(); + carrierSupportedSatelliteServicesPerProvider.putIntArray( + "00102", expectedSupportedServices3); + carrierSupportedSatelliteServicesPerProvider.putIntArray("00103", supportedServices); + mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager + .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE, + carrierSupportedSatelliteServicesPerProvider); + for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair + : mCarrierConfigChangedListenerList) { + pair.first.execute(() -> pair.second.onCarrierConfigChanged( + /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0) + ); + } + processAllMessages(); + + supportedSatelliteServices = + testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101"); + assertNotNull(supportedSatelliteServices); + assertTrue(Arrays.equals(expectedSupportedServices1, + supportedSatelliteServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + supportedSatelliteServices = + testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102"); + assertNotNull(supportedSatelliteServices); + assertTrue(Arrays.equals(expectedSupportedServices3, + supportedSatelliteServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + supportedSatelliteServices = + mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00103"); + assertTrue(supportedSatelliteServices.isEmpty()); + + // Subscriptions changed + int[] newActiveSubIds = {SUB_ID1}; + doReturn(newActiveSubIds).when(mMockSubscriptionManagerService).getActiveSubIdList(true); + for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair + : mCarrierConfigChangedListenerList) { + pair.first.execute(() -> pair.second.onCarrierConfigChanged( + /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0) + ); + } + processAllMessages(); + + supportedSatelliteServices = + testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101"); + assertTrue(supportedSatelliteServices.isEmpty()); + supportedSatelliteServices = + testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102"); + assertTrue(supportedSatelliteServices.isEmpty()); + + supportedSatelliteServices = + testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00101"); + assertNotNull(supportedSatelliteServices); + assertTrue(Arrays.equals(expectedSupportedServices1, + supportedSatelliteServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + supportedSatelliteServices = + testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00102"); + assertNotNull(supportedSatelliteServices); + assertTrue(Arrays.equals(expectedSupportedServices3, + supportedSatelliteServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + supportedSatelliteServices = + testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00103"); + assertTrue(supportedSatelliteServices.isEmpty()); } private void resetSatelliteControllerUTEnabledState() { logd("resetSatelliteControllerUTEnabledState"); setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE); - doReturn(true).when(mMockSatelliteModemInterface) + doNothing().when(mMockSatelliteModemInterface) .setSatelliteServicePackageName(anyString()); mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService"); processAllMessages(); @@ -1438,7 +1612,7 @@ public class SatelliteControllerTest extends TelephonyTest { // Reset all cached states setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE); - doReturn(true).when(mMockSatelliteModemInterface) + doNothing().when(mMockSatelliteModemInterface) .setSatelliteServicePackageName(anyString()); mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService"); processAllMessages(); @@ -1455,7 +1629,8 @@ public class SatelliteControllerTest extends TelephonyTest { private void resetSatelliteControllerUTToOffAndProvisionedState() { resetSatelliteControllerUTToSupportedAndProvisionedState(); - sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null); + // Clean up pending resources and move satellite controller to OFF state. + sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_UNAVAILABLE, null); processAllMessages(); verifySatelliteEnabled(false, SATELLITE_ERROR_NONE); } @@ -1568,6 +1743,9 @@ public class SatelliteControllerTest extends TelephonyTest { SatelliteException exception = (error == SATELLITE_ERROR_NONE) ? null : new SatelliteException(error); doAnswer(invocation -> { + if (exception == null && !enabled) { + sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null); + } Message message = (Message) invocation.getArguments()[2]; AsyncResult.forMessage(message, null, exception); message.sendToTarget(); diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java index 418d0aaede..5e1129747e 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java @@ -36,6 +36,7 @@ import android.os.ResultReceiver; import android.telecom.Call; import android.telecom.Connection; import android.telephony.BinderCacheManager; +import android.telephony.CarrierConfigManager; import android.telephony.ServiceState; import android.telephony.SubscriptionManager; import android.telephony.ims.RegistrationManager; @@ -96,6 +97,14 @@ public class SatelliteSOSMessageRecommenderTest extends TelephonyTest { when(mMockContext.getResources()).thenReturn(mResources); when(mResources.getString(com.android.internal.R.string.config_satellite_service_package)) .thenReturn(""); + when(mMockContext.getSystemServiceName(CarrierConfigManager.class)) + .thenReturn("CarrierConfigManager"); + when(mMockContext.getSystemService(CarrierConfigManager.class)) + .thenReturn(mCarrierConfigManager); + when(mMockContext.getSystemServiceName(SubscriptionManager.class)) + .thenReturn("SubscriptionManager"); + when(mMockContext.getSystemService(SubscriptionManager.class)) + .thenReturn(mSubscriptionManager); mTestSatelliteController = new TestSatelliteController(mMockContext, Looper.myLooper()); mTestImsManager = new TestImsManager( diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java new file mode 100644 index 0000000000..ba1fb9ea39 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java @@ -0,0 +1,198 @@ +/* + * 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.satellite; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.PersistableBundle; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.telephony.TelephonyTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class SatelliteServiceUtilsTest extends TelephonyTest { + private static final String TAG = "SatelliteServiceUtilsTest"; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + MockitoAnnotations.initMocks(this); + logd(TAG + " Setup!"); + } + + @After + public void tearDown() throws Exception { + logd(TAG + " tearDown"); + super.tearDown(); + } + + @Test + public void testParseSupportedSatelliteServicesFromStringArray() { + // Parse correct format input string + int[] expectedServices1 = {2, 3}; + int[] expectedServices2 = {3}; + String[] supportedServicesStrArr1 = {"10011:2,3", "10112:3"}; + Map<String, Set<Integer>> supportedServiceMap = + SatelliteServiceUtils.parseSupportedSatelliteServices(supportedServicesStrArr1); + + assertTrue(supportedServiceMap.containsKey("10011")); + Set<Integer> supportedServices = supportedServiceMap.get("10011"); + assertTrue(Arrays.equals(expectedServices1, + supportedServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + assertTrue(supportedServiceMap.containsKey("10112")); + supportedServices = supportedServiceMap.get("10112"); + assertTrue(Arrays.equals(expectedServices2, + supportedServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + // Parse correct mixed with incorrect format input string + String[] supportedServicesStrArr2 = {"10011:2,3,1xy", "10112:3,70", "10012:"}; + supportedServiceMap = SatelliteServiceUtils.parseSupportedSatelliteServices( + supportedServicesStrArr2); + + assertTrue(supportedServiceMap.containsKey("10011")); + supportedServices = supportedServiceMap.get("10011"); + assertTrue(Arrays.equals(expectedServices1, + supportedServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + assertTrue(supportedServiceMap.containsKey("10112")); + supportedServices = supportedServiceMap.get("10112"); + assertTrue(Arrays.equals(expectedServices2, + supportedServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + assertTrue(supportedServiceMap.containsKey("10012")); + assertTrue(supportedServiceMap.get("10012").isEmpty()); + + // Parse an empty input string + String[] supportedServicesStrArr3 = {}; + supportedServiceMap = SatelliteServiceUtils.parseSupportedSatelliteServices( + supportedServicesStrArr3); + assertTrue(supportedServiceMap.isEmpty()); + } + + @Test + public void testParseSupportedSatelliteServicesFromPersistableBundle() { + PersistableBundle supportedServicesBundle = new PersistableBundle(); + String plmn1 = "10101"; + String plmn2 = "10102"; + String plmn3 = "10103"; + int[] supportedServicesForPlmn1 = {1, 2, 3}; + int[] supportedServicesForPlmn2 = {3, 4, 100}; + int[] expectedServicesForPlmn1 = {1, 2, 3}; + int[] expectedServicesForPlmn2 = {3, 4}; + + // Parse an empty bundle + Map<String, Set<Integer>> supportedServiceMap = + SatelliteServiceUtils.parseSupportedSatelliteServices(supportedServicesBundle); + assertTrue(supportedServiceMap.isEmpty()); + + // Add some more fields + supportedServicesBundle.putIntArray(plmn1, supportedServicesForPlmn1); + supportedServicesBundle.putIntArray(plmn2, supportedServicesForPlmn2); + supportedServicesBundle.putIntArray(plmn3, new int[0]); + + supportedServiceMap = + SatelliteServiceUtils.parseSupportedSatelliteServices(supportedServicesBundle); + assertEquals(3, supportedServiceMap.size()); + + assertTrue(supportedServiceMap.containsKey(plmn1)); + Set<Integer> supportedServices = supportedServiceMap.get(plmn1); + assertTrue(Arrays.equals(expectedServicesForPlmn1, + supportedServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + assertTrue(supportedServiceMap.containsKey(plmn2)); + supportedServices = supportedServiceMap.get(plmn2); + assertTrue(Arrays.equals(expectedServicesForPlmn2, + supportedServices.stream() + .mapToInt(Integer::intValue) + .toArray())); + + assertTrue(supportedServiceMap.containsKey(plmn3)); + supportedServices = supportedServiceMap.get(plmn3); + assertTrue(supportedServices.isEmpty()); + } + + @Test + public void testMergeSupportedSatelliteServices() { + String plmn1 = "00101"; + String plmn2 = "00102"; + String plmn3 = "00103"; + + Integer[] providerSupportedServicesForPlmn1 = {1, 2, 3}; + Integer[] providerSupportedServicesForPlmn2 = {3, 4}; + Map<String, Set<Integer>> providerSupportedServicesMap = new HashMap<>(); + providerSupportedServicesMap.put( + plmn1, new HashSet<>(Arrays.asList(providerSupportedServicesForPlmn1))); + providerSupportedServicesMap.put( + plmn2, new HashSet<>(Arrays.asList(providerSupportedServicesForPlmn2))); + + Integer[] carrierSupportedServicesForPlmn2 = {3}; + Integer[] carrierSupportedServicesForPlmn3 = {1, 3, 4}; + Map<String, Set<Integer>> carrierSupportedServicesMap = new HashMap<>(); + carrierSupportedServicesMap.put( + plmn2, new HashSet<>(Arrays.asList(carrierSupportedServicesForPlmn2))); + carrierSupportedServicesMap.put( + plmn3, new HashSet<>(Arrays.asList(carrierSupportedServicesForPlmn3))); + + // {@code plmn1} is present in only provider support services. + int[] expectedSupportedServicesForPlmn1 = {1, 2, 3}; + // Intersection of {3,4} and {3}. + int[] expectedSupportedServicesForPlmn2 = {3}; + Map<String, Set<Integer>> supportedServicesMap = + SatelliteServiceUtils.mergeSupportedSatelliteServices( + providerSupportedServicesMap, carrierSupportedServicesMap); + + assertEquals(2, supportedServicesMap.size()); + assertTrue(supportedServicesMap.containsKey(plmn1)); + assertTrue(supportedServicesMap.containsKey(plmn2)); + assertFalse(supportedServicesMap.containsKey(plmn3)); + assertTrue(Arrays.equals(expectedSupportedServicesForPlmn1, + supportedServicesMap.get(plmn1).stream() + .mapToInt(Integer::intValue) + .toArray())); + assertTrue(Arrays.equals(expectedSupportedServicesForPlmn2, + supportedServicesMap.get(plmn2).stream() + .mapToInt(Integer::intValue) + .toArray())); + } +} diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java index 9898353178..0358809584 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java @@ -269,6 +269,8 @@ public class SubscriptionDatabaseManagerTest extends TelephonyTest { private final List<String> mAllColumns; + private boolean mDatabaseChanged; + SubscriptionProvider() { mAllColumns = SimInfo.getAllColumns(); } @@ -378,7 +380,17 @@ public class SubscriptionDatabaseManagerTest extends TelephonyTest { @Override public Bundle call(String method, @Nullable String args, @Nullable Bundle bundle) { - return new Bundle(); + Bundle result = new Bundle(); + if (method.equals(SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME)) { + result.putBoolean( + SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_DATABASE_UPDATED, + mDatabaseChanged); + } + return result; + } + + public void setRestoreDatabaseChanged(boolean changed) { + mDatabaseChanged = changed; } } @@ -422,7 +434,7 @@ public class SubscriptionDatabaseManagerTest extends TelephonyTest { .that(mDatabaseManagerUT.getSubscriptionInfoInternal(subId)).isEqualTo(subInfo); // Load subscription info from the database. - mDatabaseManagerUT.reloadDatabase(); + mDatabaseManagerUT.reloadDatabaseSync(); processAllMessages(); // Verify the database value is same as the inserted one. diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java index 06dd17cc47..aea79656c1 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java @@ -69,8 +69,10 @@ import android.annotation.NonNull; import android.app.AppOpsManager; import android.app.PropertyInvalidatedCache; import android.compat.testing.PlatformCompatChangeRule; +import android.content.ContentValues; import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -174,6 +176,8 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { mContextFixture.putBooleanResource(com.android.internal.R.bool .config_subscription_database_async_update, true); mContextFixture.putIntArrayResource(com.android.internal.R.array.sim_colors, new int[0]); + mContextFixture.putResource(com.android.internal.R.string.default_card_name, + FAKE_DEFAULT_CARD_NAME); mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC); setupMocksForTelephonyPermissions(Build.VERSION_CODES.UPSIDE_DOWN_CAKE); @@ -829,6 +833,8 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { doReturn(result).when(mEuiccController).blockingGetEuiccProfileInfoList(eq(2)); doReturn(TelephonyManager.INVALID_PORT_INDEX).when(mUiccSlot) .getPortIndexFromIccId(anyString()); + doReturn(FAKE_ICCID1).when(mUiccController).convertToCardString(eq(1)); + doReturn(FAKE_ICCID2).when(mUiccController).convertToCardString(eq(2)); mSubscriptionManagerServiceUT.updateEmbeddedSubscriptions(List.of(1, 2), null); processAllMessages(); @@ -849,6 +855,9 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { assertThat(subInfo.isEmbedded()).isTrue(); assertThat(subInfo.isRemovableEmbedded()).isFalse(); assertThat(subInfo.getNativeAccessRules()).isEqualTo(FAKE_NATIVE_ACCESS_RULES1); + // Downloaded esim profile should contain proper cardId + assertThat(subInfo.getCardId()).isEqualTo(1); + assertThat(subInfo.getCardString()).isEqualTo(FAKE_ICCID1); subInfo = mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(2); assertThat(subInfo.getSubscriptionId()).isEqualTo(2); @@ -865,6 +874,9 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { assertThat(subInfo.isEmbedded()).isTrue(); assertThat(subInfo.isRemovableEmbedded()).isFalse(); assertThat(subInfo.getNativeAccessRules()).isEqualTo(FAKE_NATIVE_ACCESS_RULES2); + // Downloaded esim profile should contain proper cardId + assertThat(subInfo.getCardId()).isEqualTo(2); + assertThat(subInfo.getCardString()).isEqualTo(FAKE_ICCID2); } @Test @@ -1084,6 +1096,19 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { } @Test + public void testGetSubscriptionUserHandleUnknownSubscription() { + mContextFixture.addCallingOrSelfPermission( + Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION); + + // getSubscriptionUserHandle() returns null when subscription is not available on the device + assertThat(mSubscriptionManagerServiceUT.getSubscriptionUserHandle(10)) + .isEqualTo(null); + + mContextFixture.removeCallingOrSelfPermission( + Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION); + } + + @Test public void testIsSubscriptionAssociatedWithUser() { insertSubscription(FAKE_SUBSCRIPTION_INFO1); @@ -1891,12 +1916,16 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { assertThat(mSubscriptionManagerServiceUT.getSlotIndex(1)).isEqualTo(0); assertThat(mSubscriptionManagerServiceUT.getPhoneId(1)).isEqualTo(0); + SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT + .getSubscriptionInfoInternal(1); + assertThat(subInfo.getDisplayName()).isEqualTo("CARD 1"); + mSubscriptionManagerServiceUT.setCarrierId(1, FAKE_CARRIER_ID1); mSubscriptionManagerServiceUT.setDisplayNameUsingSrc(FAKE_CARRIER_NAME1, 1, SubscriptionManager.NAME_SOURCE_SIM_SPN); mSubscriptionManagerServiceUT.setCarrierName(1, FAKE_CARRIER_NAME1); - SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT + subInfo = mSubscriptionManagerServiceUT .getSubscriptionInfoInternal(1); assertThat(subInfo.getSubscriptionId()).isEqualTo(1); assertThat(subInfo.getSimSlotIndex()).isEqualTo(0); @@ -2314,9 +2343,6 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { @Test public void testInactiveSimInserted() { - mContextFixture.putResource(com.android.internal.R.string.default_card_name, - FAKE_DEFAULT_CARD_NAME); - doReturn(0).when(mUiccSlot).getPortIndexFromIccId(eq(FAKE_ICCID1)); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE); @@ -2333,16 +2359,40 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { } @Test - public void testRestoreAllSimSpecificSettingsFromBackup() { + public void testRestoreAllSimSpecificSettingsFromBackup() throws Exception { assertThrows(SecurityException.class, () -> mSubscriptionManagerServiceUT.restoreAllSimSpecificSettingsFromBackup( new byte[0])); + insertSubscription(FAKE_SUBSCRIPTION_INFO1); mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE); - // TODO: Briefly copy the logic from TelephonyProvider to - // SubscriptionDatabaseManagerTest.SubscriptionProvider + + // getSubscriptionDatabaseManager().setWifiCallingEnabled(1, 0); + + // Simulate restoration altered the database directly. + ContentValues cvs = new ContentValues(); + cvs.put(SimInfo.COLUMN_WFC_IMS_ENABLED, 0); + mSubscriptionProvider.update(Uri.withAppendedPath(SimInfo.CONTENT_URI, "1"), cvs, null, + null); + + // Setting this to false to prevent database reload. + mSubscriptionProvider.setRestoreDatabaseChanged(false); mSubscriptionManagerServiceUT.restoreAllSimSpecificSettingsFromBackup( new byte[0]); + + SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT + .getSubscriptionInfoInternal(1); + // Since reload didn't happen, WFC should remains enabled. + assertThat(subInfo.getWifiCallingEnabled()).isEqualTo(1); + + // Now the database reload should happen + mSubscriptionProvider.setRestoreDatabaseChanged(true); + mSubscriptionManagerServiceUT.restoreAllSimSpecificSettingsFromBackup( + new byte[0]); + + subInfo = mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(1); + // Since reload didn't happen, WFC should remains enabled. + assertThat(subInfo.getWifiCallingEnabled()).isEqualTo(0); } @Test @@ -2494,7 +2544,7 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { .getSubscriptionInfoInternal(1); assertThat(subInfo.getSubscriptionId()).isEqualTo(1); assertThat(subInfo.getIccId()).isEqualTo(FAKE_ICCID1); - assertThat(subInfo.getDisplayName()).isEqualTo(""); + assertThat(subInfo.getDisplayName()).isEqualTo("CARD 1"); assertThat(subInfo.getDisplayNameSource()).isEqualTo( SubscriptionManager.NAME_SOURCE_UNKNOWN); assertThat(subInfo.getMcc()).isEqualTo(""); @@ -2506,7 +2556,7 @@ public class SubscriptionManagerServiceTest extends TelephonyTest { subInfo = mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(2); assertThat(subInfo.getSubscriptionId()).isEqualTo(2); assertThat(subInfo.getIccId()).isEqualTo(FAKE_ICCID2); - assertThat(subInfo.getDisplayName()).isEqualTo(""); + assertThat(subInfo.getDisplayName()).isEqualTo("CARD 2"); assertThat(subInfo.getDisplayNameSource()).isEqualTo( SubscriptionManager.NAME_SOURCE_UNKNOWN); assertThat(subInfo.getMcc()).isEqualTo(FAKE_MCC2); diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java index 143d7c9872..15fb72933b 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java @@ -570,7 +570,7 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { public Void answer(InvocationOnMock invocation) throws Throwable { currentFileId.set((String) invocation.getArguments()[6]); Message message = (Message) invocation.getArguments()[8]; - AsyncResult ar = new AsyncResult(null, new int[]{2}, null); + AsyncResult ar = new AsyncResult(null, new IccIoResult(0x90, 0x00, ""), null); message.obj = ar; message.sendToTarget(); return null; @@ -668,7 +668,7 @@ public class UiccCarrierPrivilegeRulesTest extends TelephonyTest { public Void answer(InvocationOnMock invocation) throws Throwable { currentFileId.set((String) invocation.getArguments()[6]); Message message = (Message) invocation.getArguments()[8]; - AsyncResult ar = new AsyncResult(null, new int[]{2}, null); + AsyncResult ar = new AsyncResult(null, new IccIoResult(0x90, 0x00, ""), null); message.obj = ar; message.sendToTarget(); return null; |